wtorek, 18 października 2011

Warsjawa 2011


Warsjawa 2011

W tym roku po raz drugi wybrałem się na Warsjawę. Aby dotrzeć do stolicy musiałem wstać o 4.30. Większość moich znajomych (programistów) wie jak wygląda 4:30 na zegarku jedynie przez to, że patrzą o której kładą się spać :P. Wstawało się ciężko, szczególnie że była sobota. Tym razem cały event odbył się w budynku Politechniki Warszawskiej na wydziale elektroniki i Technik Informatycznych. Podczas tegorocznej Warsjawy możliwa była do wybrania 1 z aż 4 ścieżek uczestnictwa:

Ścieżka 1 : "Google Web Toolkit krok po kroku"
Ścieżka 2 : "Meet my Android", prowadzący Mateusz Grzechociński
Ścieżka 3 : Warsztaty z projektem Domain Driven Design i Command-query Responsibility Segregation Leaven, prowadzący Sławek Sobótka i Rafał Jamróz

Ścieżka 4
Warsztaty "Obiektowa gimnastyka", prowadzący Krzysztof Jelski i Paweł Lipiński
"WebSphere Application Server v8.0 + OSGi" Grzegorz Abramczyk, prezentacja
"Do czego jeszcze biblioteki testowe" Bartek Kuczyński, prezentacja z kodowaniem na żywo
"Java EE 6 Web Profile z Apache TomEE" Jacek Laskowski, prezentacja

Ja wybrałem się na GWT. Zaraz po rozpoczęciu warsztatów uczestników czekała niemiła niespodzianka – wysiadło zasilanie. Na sali było zgromadzonych grubo ponad 40 osób, a każda miała swoje przenośne elektroniczne liczydło. Po wpięciu wszystkich maszyn do sieci wywaliło korki. Aby zapewnić prowadzącemu prąd uczestnicy połączyli ze sobą kilka listew zasilających i doprowadzili prąd z korytarza.

Moim zdaniem prowadzący warsztaty był bardzo dobrze przygotowany. Widać, że wiele czasu poświęcił, aby się przygotować na wystąpienie. W sposób wystarczający przedstawił na początku koncepcję frameworka od Googla i praktycznie od razu przeszliśmy do kodzenia. Podczas pisania prostej aplikacji typu hello world objaśniał proces: tworzenia kodu po stronie serwera i klienta, sposób debugowania i kompilowanie kodu java do java script. Bardzo dobrym pomysłem był plan, aby pod koniec warsztatów uczestnicy napisali wspólnie aplikację. Niestety nie wypalił on ponieważ wystąpił problem z siecią i uczestnicy nie mogli się połączyć z laptopem prowadzącego.

Około godziny 14 przyszła pora na długo wyczekiwany przeze mnie posiłek. Dostarczono pizzę :D.

Niestety opuściłem ostatnią część warsztatu Pawła o GWT i poszedłem na wykład Jacka Laskowskiego. Muszę się przyznać, że to on zainspirował mnie do rozpoczęcia nauki Javy. Uczestniczyłem kiedyś w jego wykładzie na mojej byłej uczelni i praktycznie zaraz po zakończeniu szybko pobiegłem po książkę o Javie :P.

Ogólnie całość oceniam bardzo dobrze. Widać że organizator – Jug bardzo się postarał.

W przyszłości na pewno jeszcze przyjadę na Warsjawę.

Dla wiadomości organizatorów: było również kilka osób z Katowic.

 I jeszcze ja :D Coś hard coduje w tle: 


Zdjęcie pożyczone z: https://picasaweb.google.com/103530179614305430424/Warsjawa2011
*) czarny sweter, koszula, okulary długie włosy :P

niedziela, 27 marca 2011

Internetowa Aplikacja w Eclipse cz. 1

W przypływie kilku wolnych chwil postanowiłem wrzucić nowy post na mojego bloga. Postaram się w poniższym tekście opisać typową aplikację typu Witaj Świecie stworzoną przy pomocy Eclipse z wykorzystaniem JPA i JSF. Końcowym etapem będzie odpalanie aplikacji na serwerze Apache Tomcat. Jako bazę danych wykorzystam popularny silnik bazodanowy MySQL.

Pierwszym krokiem jest ściągnięcie wszystkich potrzebnych rzeczy takich jak:
Eclipse - http://www.eclipse.org/downloads/ - wybieramy wersję dla programistów Java EE
Sterownik do bazy danych MySQL ze strony - http://www.mysql.com/downloads/connector/j/
Bibliotekę JSTL - http://jstl.java.net/download.html – pobieramy dwa pliki: JSTL Api oraz JSTL Implementation
Serwer na którym odpalimy naszą końcową aplikację - http://tomcat.apache.org/download-70.cgi – najnowsza wersja 7.x powinna dać radę :P

Po pobraniu wszystkiego rozpakowujemy paczkę z Eclipse powiedzmy na dysku c:\javaee\eclipse. Odpalamy aplikację i wskazujemy folder c:\javaee\workspace jako folder gdzie chcemy zapisywać nasze wszystkie projekty. Po otworzeniu Eclipse klikamy w New wybieramy Other... i z listy zaznaczamy Dynamic Web Project znajdujący się w katalogu Web. Zatwierdzamy klikając w Next i przechodzimy do kolejnego okna. Wpisujemy nazwę projektu HelloWorld w polu Project Name. W Configuration wybieramy custom i klikamy w Modify. Na liście zaznaczamy: Dynamic Web Module, JavaServer Faces oraz JPA. Zatwierdzamy klikając w OK. Następnie dwa razy klikamy w Next. W kolejnym oknie zaznaczamy Generate web.xml deployment descriptor i przechodzimy dalej klikając Next. Kolejne okno pozwala na skonfigurowanie podstawowych parametrów JPA, które użyjemy w naszej aplikacji. W sekcji JPA implementation klikamy w ikonkę oznaczoną jako: Download library... Z listy jaka się nam ukazała wybieramy EclipseLink 2.1.2 – Helios i klikamy w Next. Akceptujemy postanowienia umowy i klikamy w Finish. Rozpoczęty proces pobierze wybraną przez nas implementację technologii JPA. Po pobraniu powinniśmy powrócić do poprzedniego okna w którym klikamy w Add connection... Wybieramy z listy oczywiście MySQL, nazywamy połączenie HelloMySQL i klikamy w Next. W otwartym oknie klikamy w ikonkę New Driver Definition i z listy zaznaczamy MySQL JDBC Driver w wersji 5.1 (podejrzewam że taką pobraliście bądź nowszą). W zakładce JAR List klikamy Clear All i Add JAR/Zip. Wybieramy pobrany wcześniej sterownik, który należy rozpakować na c:\javaee\mysql-connector-java-5.1.15-bin.jar a następnie wybrać. (Wybieramy archiwum jar a nie zip!). Zatwierdzamy OK. W otwartym oknie konfigurujemy połączenie z serwerem bazodanowym. Zaznaczamy Save password i sprawdzamy czy podaliśmy prawidłowe dane klikając w Test Connection. Klikamy w Next i Finish. Kolejne okno pozwala na konfigurację JavaServer Faces. Musimy wskazać konkretną implementację tej technologii. Niestety jej nie pobraliśmy więc zmuśmy Eclipse do zrobienia tego za nas. Klikamy w Download library i zaznaczamy z listy JSF 2.0 Apache Myfaces JSF Core... Klikamy Next akceptujemy postanowienia umowy i klikamy Finish. Biblioteka JSF zostanie pobrana automatycznie. Klikamy w Finish. To już koniec procesu tworzenia nowej aplikacji. Jeżeli pojawiła się nam duża powitalna plansza zamykamy ją, a naszym oczom ukaże się godne polecenia środowisko programistyczne z otwartym projektem.

Następny proces to wrzucenie plików: mysql-connector-java-5.1.15-bin.jar, jstl-api-1.2.jar oraz jstl-impl-1.2.jar do katalogu: WebContent/lib. Jest to najprostsza forma dołączenia zewnętrznych bibliotek do projektu. W kolejnej części wpisu powiem w jaki sposób zmusić serwer, aby w momencie włączania nawiązał połączenie z bazą danych i podczas wyłączenia również się rozłączył. Aby zaimplementować klasę słuchacza należy do projektu dorzucić jeszcze jeden plik jar, bez którego ani rusz. Klikamy prawym przyciskiem myszy na projekt i wybieramy Properties. Przechodzimy do zakładki Java Build Path i klikamy Add External JARs. Przechodzimy do katalogu gdzie rozpakowaliśmy serwer Tomcat i wybieramy katalog lib. Wybieramy plik servlet-api.jar. To sprawi że ta biblioteka będzie widoczna w naszym projecie. Drugim krokiem to edycja deskryptora wdrożenia znajdującego się w pliku WebContent/WEB-INF/web.xml. Usuwamy najlepiej wszystko co w nim znajdziemy i wklejamy poniższy kod:


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>HelloWorld</display-name>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>


Plik ten pozwala na skorzystanie z technologii JSF w naszej aplikacji i jednocześnie wskazuje w jaki sposób będzie się można „dostać” do naszych podstron oraz jaka podstrona będzie domyślnie ładowaną odpowiedzią. Stwórzmy pierwszą podstronę wraz z ziarnem, które podłączymy razem. Klikając prawym przyciskiem myszy na nasz projekt wybieramy New, Other, Web i HTML File. Zatwierdzamy przyciskiem Next i wpisujemy nazwę nowego pliku: index.html i kończymy klikając Finish. Stwórzmy również klasę, w której znajdzie się logika naszej aplikacji ponowie klikając prawym klawiszem wybierając New a następnie Class. Uzupełniamy pole package wpisem: org.axlinuxprogramming.bean i nazywamy naszą klasę: MyBean. Każde ziarenko, z którego korzystamy powinno spełniać kilka prostych reguł:
  • powinno posiadać domyślny publiczny konstruktor – nie przyjmujący innych parametrów.
  • Każde pole jest prywatne i powinno zawierać metody pozwalające zapisać oraz odczytać tą wartość – geter i seter.
  • Klasa powinna być Serializowalna, choć nie zawsze jest to potrzebne.

Zanim wszystko ze sobą ładnie połączymy dokończmy sprawę z JPA. Otwieramy plik persistence.xml znajdujący się w: src/META-INF. Wklejamy do niego poniższą konfigurację. Plik ten zawiera podstawowe informacje w jaki sposób połączyć się z bazą danych, a także np. klasy encji. Poniższy plik oczywiście modyfikujemy tak, aby odpowiadał wymogom naszej bazy danych.


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="HelloWorld" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/helloworld"/>
<property name="javax.persistence.jdbc.password" value="QwertY"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="helloworld"/>
<property name="eclipselink.ddl-generation" value="create-tables"/>
</properties>
</persistence-unit>
</persistence>


W pliku możemy zdefiniować kilka połączeń z bazą danych. Przykładem może być sytuacja, w której z innej bazy korzystamy podczas tworzenia aplikacji, a z innej gdy wdrażamy aplikację. Pojedynczą konfigurację definiujemy pomiędzy znacznikami persistence-unit. Nasze połączenie nosi nazwę HelloWorld i oprócz danych o połączeniu zawiera informację o tym, że JPA ma utworzyć tabele w przypadku gdy klasy encji będą istnieć, a tabele nie. Stwórzmy jeszcze jedną klasę, nazwijmy ją DBHelper i umieśćmy w pakiecie org.axlinixprogramming.db. Klasa ta będzie odpowiadać za całą ceremonię łączenia się z bazą danych, inicjowania połączenia, a także jego zakończenia. Klasa ta oparta jest o wzorzec projektowy – singleton. Dzięki czemu mamy jedynie jedną instancję klasy DBHelper dla całej aplikacji. Sam proces tworzenia EntityManagerFactory jest czasochłonny i wystarczy, aby istniał jeden obiekt dla całej aplikacji. Dzięki tej klasie pozbyliśmy się problemu z „wstawaniem” całego JPA podczas wywołania pojedynczego żądania
oraz uchroniliśmy się przed zagrożeniami czyhającymi na nas w aplikacjach wielowątkowych.



package org.axlinuxprogramming.db;


import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;


public class DBHelper
{
private static DBHelper instance;
private EntityManagerFactory emf;

private final String UNIT_NAME = "HelloWorld";

private DBHelper(){}

public synchronized static DBHelper getManager()
{
if(instance == null)
instance = new DBHelper();

return instance;
}

public EntityManagerFactory createEntityManagerFactory()
{
if(emf == null)
emf = Persistence.createEntityManagerFactory(UNIT_NAME);

return emf;
}

public EntityManager createEntityManager()
{
return createEntityManagerFactory().createEntityManager();
}

public void closeEntityManagerFactory()
{
if(emf != null)
emf.close();
}
}



 Pytanie teraz brzmi jak poradzić sobie z wstaniem JPA podczas uruchomienia serwera i jak zakończyć prawidłowo jego działanie gdy serwer będzie wyłączany. Rozwiązaniem jest stworzenie słuchacza, który wykryje gdy serwer budzi się do życia bądź będzie zmierzał ku wyłączeniu i wykona nasz kod. Stwórzmy jeszcze jedną klasę o nazwie HelloWorldListener w pakiecie org.axlinux.listener i wrzućmy do niej taki oto kod:




package org.axlinux.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.axlinuxprogramming.db.DBHelper;

public class HelloWorldListener implements ServletContextListener
{

@Override
public void contextDestroyed(ServletContextEvent arg0)
{
//Zamykam połączenie z bazą
DBHelper.getManager().closeEntityManagerFactory();
}

@Override
public void contextInitialized(ServletContextEvent arg0)
{
//Rozpoczynam połączenie z bazą
DBHelper.getManager().createEntityManagerFactory();
}
}

Teraz należy dodać jeszcze do web.xml taki oto wpis od razu wewnątrz znacznika: web-app. Pozwoli to na odnalezienie klasy serwerowi i dopalenie naszego kodu podczas procesu włączenia i wyłączenia serwera.


<listener>
<description>HelloWorldListener</description>
<listener-class>org.axlinux.listener.HelloWorldListener</listener-class>
</listener>

Teraz przyszła pora na zabawę – wygenerujmy klasę encję i wstawmy kilka wyników do bazy, a następnie wyświetlmy wszystko na stronie. Klikamy prawym w projekt i wybieramy New, a następnie Other, JPA i zaznaczamy Entity i klikamy Next. W otwartym oknie uzupełniamy nazwę pakietu: org.axlinuxprogramming.entity i wpisujemy klasę: Post. Klikamy następnie w Next. Za pomocą przycisku Add dodajemy kilka kolumn jak: id typu int, title (String), content (String), author (String). Zaznaczamy przy id checkboxa dla kolumny key. Klikając Finish dodajemy nową klasę do projektu. Wygenerowana klasa powinna wyglądać mniej więcej tak jak moją:



package org.axlinuxprogramming.entity;

import java.io.Serializable;
import java.lang.String;
import javax.persistence.*;

/**
* Entity implementation class for Entity: Post
*
*/

@Entity
public class Post implements Serializable {

@Id
@GeneratedValue (strategy = GenerationType.AUTO)
private int id;
private String title;
private String content;
private String author;
private static final long serialVersionUID = 1L;

public Post() {
super();
}

public Post(String title, String content, String author) {
super();
this.title = title;
this.content = content;
this.author = author;
}

public int getId() {
return this.id;
}

public void setId(int id) {
this.id = id;
}
public String getTitle() {
return this.title;
}

public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return this.content;
}

public void setContent(String content) {
this.content = content;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

}



Zwróć uwagę, że dodałem dodatkową adnotację: @GeneratedValue (strategy = GenerationType.AUTO), która zadba o to, aby kolejne rekordy miały unikatowe wartości klucza. Dodałem również konstruktor przyjmujący parametry. Do pliku persistence.xml dodała się również automatycznie pojedyncza linia:


<class>org.axlinuxprogramming.entity.Post</class>


Jest to informacja o klasie, która jest encją w naszej aplikacji. Pora wrócić do klasy MyBean i stworzyć dla niej konstruktor, w którym sprawdzimy czy istnieją jakieś wpisy w naszej tabeli, a jeżeli nie to coś do niej dodamy. Kluczowym obiektem w konstruktorze jest EntityManager. Dzięki niemu jesteśmy wstanie rozpocząć nową transakcję, a także ją zakończyć. Cały kod sql-owy wykonany pomiędzy begin(), a commit() zostanie wykonany dopiero po commit(), a w przypadku gdy pojedyncze zapytanie wysypie się to JPA zadba o to aby wycofać również pozostałe zapytania.




package org.axlinuxprogramming.bean;

import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.axlinuxprogramming.db.DBHelper;
import org.axlinuxprogramming.entity.Post;


@ManagedBean(name="myBean")
@RequestScoped
public class MyBean
{

public MyBean()
{
EntityManager em = DBHelper.getManager().createEntityManager();

em.getTransaction().begin();

Query query = em.createQuery("SELECT COUNT(p.id) FROM Post p");
Number countResult=(Number) query.getSingleResult();

if(countResult.intValue() == 0)
{
Post p1 = new Post("My First Post", "It's my first story...", "Tomasz");
em.persist(p1);
Post p2 = new Post("My Secound Post", "It's my secound story...", "Tomasz");
em.persist(p2);
}
em.getTransaction().commit();
em.close();
}


public List getPosts()
{
EntityManager em = DBHelper.getManager().createEntityManager();

em.getTransaction().begin();

Query query = em.createQuery("SELECT p FROM Post p");
List result = query.getResultList();


em.getTransaction().commit();
em.close();

return result;
}

}


Moje ziarenko posiada również metodę getPosts. Mówiłem, że getery odnoszą się do pól, ale równie dobrze zamiast tego można wykorzystać je na przykład tak jak tutaj do pobrania kolekcji z bazy danych. Dzięki metodzie getPosts możemy „przenieść” kolekcję obiektów pobranych z bazy danych na stronę html. Ale zanim przejdziemy do edycji pliku index.xhtml przyjrzyjmy się adnotacjom @ManagedBean(name="myBean") i @RequestScoped. Pierwsza z nich powoduje, że nie musimy informować JSF o naszych beanach przy pomocy pliku XML. Również nazywamy nasz bean jako myBean i pod taką nazwą będzie on widoczny w aplikacji. Druga adnotacja informuje JSF, że obiekt naszej klasy jest tworzony przy każdym nowym żądaniu. Reasumując za każdym razem gdy odwiedzimy stronę, które korzysta z tej klasy na nowo zostanie utworzony obiekt. W przeciwieństwie do zasięgu sesyjnym, gdzie obiekt jest tworzony osobno dla każdego użytkownika i trwa przez sesję.


Przejdźmy teraz do pliku index.xhtml i wklejmy do niego taki oto kod:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<head>

<title>Insert title here</title>
</head>

<body>

<h2>Oto moje posty</h2>

<h:dataTable value="#{myBean.posts}" var="p" border="1">
<h:column>
<h:outputText value="#{p.id}" />
</h:column>

<h:column>
<h:outputText value="#{p.title}" />
</h:column>

<h:column>
<h:outputText value="#{p.content}" />
</h:column>

<h:column>
<h:outputText value="#{p.author}" />
</h:column>
</h:dataTable>
</body>
</html>




Pod nagłówkiem h2 wrzucamy komponent dataTable. Odpowiada on za wygenerowanie tabeli oraz jest iteratorem, dzięki któremu możemy wrzucić obiekty z kolekcji. Kod: value={#myBean.posts} pobierze z naszego ziarna kolekcję dzięki metodzie getPosts. Każdy element kolekcji zostanie podstawiony za p. Jest to pewnego rodzaju pętla foreach osadzona w dokumencie xhtml. Następnie definiujemy kolejno kolumny i odwołujemy się za pomocą EL-wyrażeń (#{}) do pól obiektów Post.

Teraz kulminacyjny moment – uruchamiamy aplikację na serwerze. Klikamy prawym przyciskiem na projekt wybieramy Run As i Run On Server. Zaznaczamy Manually define a new server i z listy wybieramy Apache, Tomcat v7.0 Server. Klikamy w Add... i w nowym oknie wybieramy lokalizację naszego serwera. Klikamy Finish, Next i Finish. Po poprawnym wystartowaniu serwera powinniśmy zobaczyć całość pod adresem: http://localhost:8080/HelloWorld/index.html.

To już koniec. Mam nadzieję, że również u Was wszystko zadziałało jak należy. Postaram się wrzucić jeszcze kolejne wpisy o tym jak np. edytować i dodawać wpisy w aplikacji – ale to innym razem.

środa, 8 grudnia 2010

Serializacja w Javie na potrzeby SCJP 6

Serializacja w Javie, a SCJP

W tym krótkim poście chciałbym opisać serializację w Javie na potrzeby egzaminu SCJP 6. Mówiąc najprościej serializacja to przekształcenie aktualnego stanu obiektu w strumień bajtów, który może być zapisany na dysku twardym. Odwrotny proces to deserializacja. Wspominajac o zachowaniu stanu obiektu mam namyśli oczywiście zachowanie wszystkich albo prawie wszystkich zmiennych instancji obiektu. Na początek kilka reguł:

Każdy obiekt, który ma być serializowany musi implementować interfejs Serializable.

Aby zapisać i odczytać stan obiektu korzystamy z klas: ObjectOutputStream i ObjectInputStream.

Prosty przykład na początek:

class Animal implements Serializable
{
public int age;

public Animal(int age)
{
this.age = age;
}

public String toString()
{
return "My age is: " + age;
}

public static void main(String[] args)
{
Animal a1 = new Animal(12);
Animal a2 = new Animal(2);

try
{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("my_animals.dat"));
oos.writeObject(a1);
oos.writeObject(a2);
oos.close();
}catch(Exception ex)
{
ex.printStackTrace();
}

a1 = null;
a2 = null;

try
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("my_animals.dat"));
a1 = (Animal) ois.readObject();
a2 = (Animal) ois.readObject();
ois.close();
}catch(Exception ex)
{
ex.printStackTrace();
}

System.out.println(a1.toString() + " " + a2.toString());
}

}



Do pliku zapisujemy stan dwóch obiektów klasy Animal, a następnie je odczytujemy. Trzeba oczywiście pamiętać o łapaniu wyjątków, w tym przykładzie nieelegancko łapię wszystkie wyjątki za jednym razem.



Na stan obiektu składają się: zmienne typu referencyjnego – wskazują obiekty w pamięci komputera, oraz zmienne przechowujące wartości typu prymitywnego np. int, char, long. Jeżeli dany obiekt posiada w sobie dowiązanie do innego obiektu np. Owner – Animal to klasa tego obiektu musi również implementować interfejs Serializable


class Animal implements Serializable
{
public int age;

public Animal(int age)
{
this.age = age;
}

public String toString()
{
return "My age is: " + age;
}
}

public class Owner implements Serializable
{
private String name;
private Animal animal;

public Owner(String name, Animal animal)
{
this.name = name;
this.animal = animal;
}
}


Sytuacja jest bajecznie prosta gdy sami piszemy każdą z klas. Co zrobić w przypadku, gdy korzystamy już z istniejącej klasy, która nie implementuje ważnego interfejsu? Z pomocą przychodzi słowo kluczowe transient. Pole klasy nim tak opatrzone nie jest serializowane. To oznacza, że ten obiekt lub wartość nie zostanie zapisana. Po deserializacji pole to będzie przyjmować null-a, gdy jest to zmienna referencyjna lub domyślną wartość dla danego typu np. 0 lub false. Warto zaznaczyć, że podczas desarializacji nie wykonuje się konstruktor klasy, do której dany obiekt należy, dlatego np. pole: cpuName będzie null-em.



public class Computer implements Serializable
{

private transient String cpuName = "CPU from China 66.6 MHz";

public String toString()
{
return "My CPU is: " + cpuName;
}

}


W przypadku próby zapisania obiektu, który nie jest serializowalny (nie implementuje Serializable) otrzymamy wyjątek: NotSerializableException.

Jeżeli bardzo nam zależy na zachowaniu stanu pól możemy zdefiniować odpowiednie metody, które pozwolą prócz samych obiektów na zachowanie innych wartości.



public class Home implements Serializable
{

private String name = "Home1";
private transient Window window = new Window(14, 34);

public String toString()
{
return "Window's size: " + window.height +" x "+ window.width;
}

private void writeObject(ObjectOutputStream oos) throws IOException
{
oos.defaultWriteObject();
oos.writeInt(window.width);
oos.writeInt(window.height);
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException
{
ois.defaultReadObject();
window = new Window(ois.readInt(), ois.readInt());
}



public static void main(String[] args)
{
Home h = new Home();


try
{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("my_home.dat"));
oos.writeObject(h);
oos.close();
}catch(Exception ex)
{
ex.printStackTrace();
}

h = null;


try
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("my_home.dat"));
h = (Home) ois.readObject();
ois.close();
}catch(Exception ex)
{
ex.printStackTrace();
}



}

}

class Window
{
public int width, height;

public Window(int w, int h)
{
width = w;
height = h;
}
}


Klasa Window nie jest serializowalna. Obiekty klasy Home posiadają pola, które są referencjami do obiektów klasy Window. Definiując metody writeObject i readObject możemy prócz obiektów zapisać również dwa int-y które są polami klasy Window. Przywrócenie odpowiednich wartości łączy się z odczytaniem wartości tych int-ów (w odpowiedniej kolejności) i wywołaniu konstruktora.


Co się dzieje w przypadku, gdy rozszerzamy daną klasę która nie implementuje Serializable?

Odpowiedź jest prosta: Jedynie te pola zostają serializowane, które należą do podklasy. Natomiast jakie wartości zostają nadane polom nad klasy po deserializacji?

Zanim odpowiem na to pytanie warto zaznaczyć, że w tym przypadku wykonują się konstruktory należące do nadklas. Pola będą przyjmować wartości nadane przez konstruktory zdefiniowane w nadklasach. Oto przykład:


public class Foo
{
String name = "Foo";
}

public class Bar extends Foo implements Serializable
{
String barName = "BarName";

public static void main(String[] args)
{
Bar bar = new Bar();
bar.name = "newFooName";

try{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("my_bar.dat"));
oos.writeObject(bar);
oos.close();
}catch(Exception ex)
{
ex.printStackTrace();
}

bar = null;

try
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("my_bar.dat"));
bar = (Bar) ois.readObject();
ois.close();
}catch(Exception ex)
{
ex.printStackTrace();
}

System.out.println(bar.barName +" "+bar.name);
//Zostanie wypisany tekst: BarName Foo

}
}


Ostatnią rzeczą jaką warto powiedzieć to, że zapisywane są jedynie pola obiektów, a nie pola klasy. Więc wszystko co opatrzone słowem static nie zostanie zachowane.

niedziela, 17 października 2010

Co jest nie tak z PHP?

Do napisania tego tekstu zabierałem się dość długo. W końcu się odważyłem. Mam zamiar podzielić się z Wami moimi przemyśleniami na temat języka PHP. Wszystkie jednak mogę podsumować następująco: PHP to najgorszy język programowania wysokiego poziomu w jakim miałem okazję pisać. Zacznijmy może od początku. Skąd się wziął pomysł na stworzenie PHP? Otóż miał to być prosty język skryptowy wymyślony po to, aby sprawić by proste strony internetowe były dynamiczne. To założenie się udało. Na tym polu pisanie prostych kilkulinijkowych skryptów jest bajecznie proste i do tego PHP nadaje się wyśmienicie. Powinno tak zostać. Niestety w PHP powstają spore aplikacje klasy Enterprise. W pisaniu większych rzeczy przeszkadza sporo rzeczy, które poniekąd wynikają z historii PHP, który nagle stał się językiem, w którym pisze się coś więcej niż prostą stronę internetową. Oto lista rzeczy, które mnie najbardziej denerwują:

- dynamiczne typowanie ze słabą typizacją – dla niektórych wygoda, a dla mnie porażka jakich mało, rozwiązanie to uniemożliwia skuteczne debugowanie. Wytłumaczę to na przykładzie: piszę sobie pewien większy kawałek kodu, uruchamiam go, a moim oczom ukazuje się biała strona zamiast wyniku. Zastanawiam się co jest źle. Nie mam żadnego wyjątku informującego mnie o tym, że przekazałem do jakiejś funkcji argument o złym typie. Aplikacja w jakimś momencie wykonała coś czego nie przewidziałem, funkcja zwróciła zamiast Stringa wartość 0, którą następnie przekazałem do innej funkcji. Idąc dalej okazuje się, że kod wykonał się bez zarzutu, a jednak wyniku nie ma. Porównam tą sytuację do przykładu w Ruby. Otrzymując nil-a z funkcji zamiast stringa,  a następnie wykonując na nim metodą np. length otrzymam taki wyjątek, że mało co nie spadam z krzesła. Druga sytuacja mająca częste miejsce to literówka w nazwie zmiennej. W PHP robię gdzieś literówkę, kod się wykona, a ja otrzymam nie poprawne dane. Ruby mnie poinformuje, że nic nie wie o nowo powstałym czymś, co się nazywa jak zmienna z literówką. No to już mamy 2 do 0 dla Ruby. Ruby to język dynamicznie typowany z silną typizacją. To oznacza, że jeżeli gdzieś potrzebujemy String-a, a podamy np. liczbę całkowitą to nie zadziała. Sami musimy się zatroszczyć o to jaki typ ma nasza zmienna. Zdecydowanie ułatwia to szukanie błędów, a konwersja pomiędzy typami jest bardzo prosta.

- programowanie obiektowe – PHP miał być prostym językiem skryptowym, pewnie dla tego nie zaimplementowali w nim od samego początku obiektowość przynajmniej na takim poziomie jak w Javie, w której jednak nie wszystko jest obiektem. Chyba każdy przyzna mi rację, że zdecydowanie lepiej jest wywołać metodę na obiekcie niż pisać w sposób proceduralny. Nienawidzę wykonywać funkcji na String-ach. W zasadzie praktycznie wszystko w PHP jest funkcjami. Dlaczego nie mogły być one od samego początku powkładane pięknie do klas. Gdy powstał PHP była końcówka XX wieku. W tym czasie było już kilka świetnych języków obiektowych, a twórcy PHP pozostali w latach 70. Ruby jest obiektowym językiem programowania. Wszystko w nim jest obiektem, do tego stopnia i nawet dalej, że można wykonać coś takiego s = 1.to_s(), co oznacza, że jedynka jest obiektem i wywołujemy na nim metodę. Tak więc mamy 3 do 0

- brak konwencji – wygląda na to, że PHP projektowało 100 osób i każda robiła po swojemu. Dzięki temu mamy między innym niejednolitą konwencję nazywania funkcji np. strstr, str_replace, ucfirst. Wszystkie trzy funkcje operują na String-ach, raz nazywają się z podkreśleniem, a raz bez, raz z przedrostkiem, a raz bez. Każdy musiał robić po swojemu. Takich perełek znajdzie się więcej. W Rubym konwencja nazewnictwa jest raczej zachowana. Chociaż podobnie jak w PHP w podstawowych klasach znajdują się metody o nazwach po których ciężko rozszyfrować do czego służą. Jak dla mnie jest 4 do 0.

- obsługa wyjątków – każdy szanujący się język programowania powinien mieć coś takiego, Dobrze by było, aby również to działało. W PHP z tego co wiem nie zawsze chce to działać. Co z tego, że wrzuciłem cały kod do bloku try catch jak i tak otrzymałem fatal error.

- PHP czyli jak to spotkało się na początku 3 programistów. Z moich obserwacji wynika, że podczas grubej imprezy w akademikach musiało wpaść na pomysł trzech programistów: Javy, C i Perla, aby zrobić nowy język programowania. Zabrali się oni do pracy zaraz gdy się obudzili i byli na dużym kacu. Mi nie podoba się składnia PHP, choć muszę przyznać, że przypadnie ona raczej do gustu programistom C bardziej niż składnia Rubego. Jak dla mnie dalej jest 4 do 0. Nikt nie dostaje punktu.

Czas na podsumowanie. Każdy w życiu ma raczej taki etap, w którym pisze w PHP. Ja ten etap mam za sobą i mam nadzieję, że nigdy nie powróci. Pomimo tego, że sam jestem negatywnie nastawiony do PHP muszę przyznać mu kilka punktów. 

Pierwszy punkt za możliwość wdrożenia aplikacji PHP. Ta technologia dostępna jest za darmo, prócz tego nie ma najmniejszego problemu ze znalezieniem serwera nawet za kilka złotych miesięcznie, który ma wbudowaną obsługę PHP. Jeden punkt dla PHP.

Drugi to wsparcie społeczności. Społeczność PHP jest spora. Bez problemu znajdziemy fora dla PeHaPowców, w miarę dobrą dokumentację i sporo bibliotek. Dla Ruby jest ciut gorzej. Często zdarza się, że bibliotekę dla Ruby znajdziemy i jest ona całkiem dobra ale gorzej już np. z dobrą dokumentacją. Nie ma co, punkt dla PHP

Wydajność. Nie rozumiem jak można było stworzyć wydajny i dość dobry interpreter PHP, który stoi zdecydowanie na wyższym poziomie niż sama koncepcja PHP, a wykombinować tak słaby język na poziomie konceptualnym.  W ostatnim czasie dla Ruby zrobiono sporo, aby pod względem wydajności zbliżyć się do czołówki, w tym do PHP. Tak więc w tym pojedynku nie ma punktów.

Wynik końcowy 4 do 2.

Jeżeli uważacie, że PHP to dobry język programowania bo łatwiej i szybciej można coś sensownego w nim napisać to polecam poczytać trochę o Ruby. Jestem fanem tego języka i polubiłem go bardzo szybko. Podczas pisania pierwszej większej aplikacji w Ruby przekonałem się, że jest on bardziej dla mnie intuicyjny niż PHP z którym znam się od liceum, chociaż moją przygodę z nim zacząłem kilka miesięcy wstecz.




poniedziałek, 4 października 2010

Własna baza danych SQLite w Android

System operacyjny Android stworzony dla urządzeń mobilnych takich jak: telefony komórkowe, tablety czy netbooki pozwala na skorzystanie z bazy danych SQLite. Sporo materiałów w Internecie pokazuje jak krok po kroku utworzyć bazę danych, a następnie wprowadzać do niej dane. W tym artykule chciałbym pokazać jak przenieść do telefonu istniejącą bazę danych. Zakładam, że plik z bazą danych może być większy niż 1MB, dlatego użyjemy kompresji. Android posiada ograniczenie, które powoduje, że nie można skorzystać z pliku zapisanego w katalogu assets większego niż 1 MB.

Zanim utworzymy archiwum .zip z bazą danych należy lekko zmodyfikować już istniejącą bazę. Plik z bazą należy otworzyć za pomocą odpowiedniego narzędzia, które pozwoli na dodanie tabeli oraz zmianę nazwy kluczy głównych. Jednym z takich narzędzi jest wtyczka dla przeglądarki internetowej Firefox o nazwie: SQLite Manager. Do istniejącej bazy należy wprowadzić nową tabelę za pomocą polecenia SQL:

CREATE TABLE "android_metadata" ("locale" TEXT DEFAULT 'en_US')

oraz dodać jeden wpis:

INSERT INTO "android_metadata" VALUES ('en_US')


Teraz dla wszystkich tabel należy zmienić klucz główny na _id;



Następnym krokiem jest utworzenie archiwum zip zawierającym jedynie plik z bazą danych. Kopiujemy go do folderu assets, który znajduje się w projekcie naszej aplikacji.



Teraz dodajemy do projektu nową klasę o nazwie: DataHelper, która rozszerza klasę: SQLiteOpenHelper. Do podanej klasy kopiujemy poniższy kod.



package org.axlinux.android;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DataHelper extends SQLiteOpenHelper
{

private static String DATABASE_DIR = "/data/data/org.axlinux.android/databases/"; //Ścieżka do katalogu gdzie przechowywane są bazy danych
private static String DATABASE_NAME = "my_books.sqlite"; //Nazwa pliku z bazą danych
private static String DATABASE_ZIP = "my_books.sqlite.zip"; //Nazwa pliku zip w którym znajduje się skompresowany plik z bazą danych
private SQLiteDatabase db;
private final Context context;

public DataHelper(Context context)
{
super(context, DATABASE_NAME, null, 1);
this.context = context;
}

@Override
public void onCreate(SQLiteDatabase arg0)
{
//Implementację tej metody można pominąć

}

@Override
public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2)
{
//Implementację tej metody można pominąć

}

/**
* Zadaniem metody jest sprawdzenie czy istnieje plik z bazą danych
* @return true jeżeli plik w katalogu istnieje,
* false gdy nie istnieje - nie został jeszcze skopiowany z assets do katalogu w którym android przechowuje pliki baz danych
*/
public boolean dataBaseExists()
{
File f = new File(DATABASE_DIR);
if(!f.exists())
f.mkdir();

boolean result = true;
SQLiteDatabase db_test = null;

try{
db_test = SQLiteDatabase.openDatabase(DATABASE_DIR+DATABASE_NAME, null, SQLiteDatabase.OPEN_READONLY);
} catch (Exception ex){
result = false;
}

if(db_test != null)
db_test.close();


return result;
}

/**
* Metoda rozpakowuje plik z assets do katalogu gdzie Android trzyma bazy danych pod warunkiem, że już nie zostało to zrobione
* @throws IOException
*/
public void copyDataBase() throws IOException
{
if(!this.dataBaseExists())
{
File f = new File(DATABASE_DIR);
if(!f.exists())
f.mkdir();

ZipInputStream zis = new ZipInputStream(context.getAssets().open(DATABASE_ZIP));
ZipEntry entry;

entry = zis.getNextEntry();

int BUFFER = 2048;
FileOutputStream fos = new FileOutputStream(DATABASE_DIR + DATABASE_NAME);
BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER);
int count;
byte data[] = new byte[BUFFER];

while ((count = zis.read(data, 0, BUFFER)) != -1)
dest.write(data, 0, count);


dest.flush();
dest.close();
zis.close();
}
}

/**
* Metoda otwiera bazę danych, po wywołaniu tej metody można wykonywać polecenie SQL
* @return - true gdy otwarcie się powiodło
*/
public boolean open()
{
boolean result = false;

try{
String myPath = DATABASE_DIR + DATABASE_NAME;
db = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE);
result = true;
} catch (Exception ex) {
//Otwarcie bazy danych nie powiodło się}
}

return result;
}

public synchronized void close() {
if(db != null)
db.close();
super.close();

}

/**
* Wykonuje zapytanie SQL
* @param query - zapytanie SQL
* @return zwraca Stringa z rezultatem
*/
public String executeQuery(String query)
{
String result = "";
Cursor cursor = db.rawQuery(query, null);

if(cursor.moveToFirst())
{
do
{
result += cursor.getString(0) +". "+ cursor.getString(1) +" "+ cursor.getString(2) +"\n";
}while(cursor.moveToNext());
}

return result;
}
}



Klasa z metodą onCreate rozszerzająca klasę Activity



package org.axlinux.android;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class Main extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

String out ="";

TextView tv = (TextView) this.findViewById(R.id.TextView01);
DataHelper dh = new DataHelper(this);

out += "Czy istnieje baza danych? " + dh.dataBaseExists()+"\n";
try
{
dh.copyDataBase();
out += "Czy istnieje baza danych po skopiowaniu? " + dh.dataBaseExists()+"\n";
out +="Rezultat otworzenia bazy danych: " + dh.open()+"\n";
out += "Wynik zapytania \n" + dh.executeQuery("SELECT * FROM books limit 3;");
} catch (Exception ex)
{
out += ex.toString() +"\n";
}

tv.setText(out);

}
}



Pierwszym krokiem po utworzeniu nowej klasy jest modyfikacja pól klasy. Należy odpowiednio zmodyfikować wartości: DATABASE_DIR, DATABASE_NAME, DATABASE_ZIP.

DATABASE_DIR to miejsce gdzie Android przechowuje pliki z bazami danych. Należy poprawnie wprowadzić nazwę głównego pakietu. DATABASE_NAME to nazwa naszej bazy danych, która jest umieszczona wewnątrz archiwum zip, a DATABASE_ZIP to nazwa archiwum z bazą danych w katalogu assets.


W metodzie onCreate możemy teraz stworzyć instancję klasy DataHelper i wywołać metodę copyDataBase. Po zakończeniu warto wywołać metodę dataBaseExists() w celu sprawdzenia rezultatu.