26 lutego 2007

Aplikacja JPA w GlassFish v2 z Firebird v2 i Eclipse IDE 3.3

0 komentarzy
Po sukcesie z Eclipse Mylar pozostałe wtyczki, z którymi miałem problem zaczęły również w końcu działać! Okazało się, że błędna konfiguracja Eclipse IDE 3.3M5, który korzystał z biblioteki Java 1.4.2 powodowała, że wtyczki zależne od Java 5 zainstalowane nie zgłaszały błędu, ale i nie działały. Odinstalowanie Java 1.4.2 z mojego komputera było nie tylko dobrym krokiem w celu poprawy sytuacji z niedziałającymi wtyczkami, ale również zwiększenia ilości wolnego miejsca na dysku (taki maluczki niedzielny prezent).

Taki sukces wymagał odpowiedniego rozgłosu! Musiał być oznajmiony w odpowiedni sposób - przepisem na przykładową aplikację JPA z Eclipse IDE, GlassFish i Firebird.

Zainstalowawszy WTP 2.0M5 (z narzędziami do JPA i JSF) oraz GlassFish Eclipse Plugin postanowiłem popróbować się z utworzeniem prostej aplikacji JPA, która korzystałaby ze źródła danych w GlassFish v2 do Firebird v2 (temat pojawił się na grupie pl.comp.lang.java - EJB3 SunAppServer i Firebird - i akurat zbiegł się z moim powrotem do lektury specyfikacji JPA). Nigdy nie pracowałem z Firebird, ale poza poznaniem podstawowych komend do uruchomienia bazy danych i jej zatrzymania nie zamierzałem poznawać produktu więcej. To jest właśnie zaleta warstwowych aplikacji, gdzie część odpowiedzialności za tworzenie aplikacji można scedować na inne osoby w projekcie (no cóż, nie pierwszy raz występuję w kilku rolach: administratora bazy danych, serwera aplikacyjnego i programisty). W zasadzie nie potrzebowałem serwera aplikacyjnego do uruchomienia aplikacji z JPA, ale zadane pytanie na p.c.l.java dotyczyło takiego środowiska, a co więcej zarządzanie połączeniami do bazy danych zlecimy serwerowi aplikacyjnemu GlassFish (poznanie kilku komend administracyjnych nie powinno przysłonić właściwej treści wpisu).

Zanim przedstawię przepis na aplikację JPA i otaczający ją świat, kilka słów o wersjach oprogramowania w środowisku wykonawczym (uruchomieniowym):
Poniższy obrazek mówi sam za siebie - Eclipse IDE 3.3 z wtyczkami, które obiecują usprawnić pracę z technologiami Java EE 5 i przybliżyć nas do (bądź wręcz przewyższyć możliwości) NetBeans IDE 6.0 (jeszcze nie tym razem ;-))


Krok 1. Definicja GlassFish v2 w Eclipse IDE 3.3

Rozpoczynam od definicji GlassFish w Eclipse IDE.


Jak widać powyżej lista dostępnych serwerów aplikacyjnych jest imponująca, ale tym razem padło na GlassFish (reszta musi poczekać).


Po chwili Eclipse IDE już wie jak pracować z GlassFish v2. Na razie idzie gładko. GlassFish jest zdefiniowany i uruchomiony - wszystko z poziomu Eclipse IDE.

Krok 2. Uruchomienie Firebird Superserver

Uruchamiam Firebird Superserver (zalecana konfiguracja na MS Windows, na którym pracuję).
    $ cd $FIREBIRD_HOME
$ ./bin/install_super.bat
, co tworzy dwa serwisy Firebird Guardian oraz Firebird Server - oba powinny już być uruchomione.


Po tym można już założyć nową bazę danych dedykowaną dla mojej przykładowej aplikacji.

$ cd $FIREBIRD_HOME

$ ./bin/isql.exe
Use CONNECT or CREATE DATABASE to specify a database
SQL> create database 'c:\temp\glassfish-jpa.fdb' page_size 8192
CON> user 'SYSDBA' password 'masterkey';
SQL> exit;

I sprawdzam, że baza danych faktycznie jest stworzona i dostępna z zewnątrz.

$ ./bin/isql.exe
Use CONNECT or CREATE DATABASE to specify a database
SQL> connect 'localhost:c:\temp\glassfish-jpa.fdb' user 'sysdba' password 'masterkey';
Database: 'localhost:c:\temp\glassfish-jpa.fdb', User: sysdba
SQL> exit;

Dowiaduję się o możliwości utworzenia synonimów (ang. alias) dla nowoutworzonej bazy danych. Korzystanie z synonimu jest prostsze i dodatkowo skrypt tworzący pulę połączeń w GF nie akceptuje dwukropka w ścieżce bazy danych (':'), który jest separatorem w opcji --property.

W pliku $FIREBIRD_HOME/aliases.conf umieściłem poniższą linię:

glassfish-jpa=c:\temp\glassfish-jpa.fdb

Krok 3. Definicja źródła danych do Firebird w GlassFish

Postępujemy zgodnie z dokumentacją GlassFish'a - Chapter 14: Using the JDBC API for Database Access.

Istnieją dwie możliwości zdefiniowania źródła danych w GlassFish:
Wybieram opcję pierwszą - polecenie asadmin create-jdbc-connection-pool.

Najpierw należy przekopiować sterownik JDBC Firebird'a - jaybird-full-2.1.1.jar - do katalogu $GLASSFISH_HOME/domains/domain1/lib/ext.

Następnie wydać polecenia z poziomu konsoli:

$ ./bin/asadmin.bat create-jdbc-connection-pool \
--datasourceclassname org.firebirdsql.jdbc.FBWrappingDataSource \
--restype javax.sql.DataSource \
--property password=masterkey:userName=sysdba:databaseName=//localhost/glassfish-jpa:pooling=true:minSize=5:maxSize=30:type=type4 \
FirebirdPool

$ ./bin/asadmin.bat ping-connection-pool FirebirdPool
Command ping-connection-pool executed successfully.

$ ./bin/asadmin.bat create-jdbc-resource --connectionpoolid FirebirdPool jdbc/Firebird

W konsoli można sprawdzić, że połączenie faktycznie zostało zdefiniowane, a jak później się okaże i dostępne dla aplikacji.

Krok 4. Utworzenie encji Customer

Mając zainstalowaną i co ważne działającą wtyczkę Eclipse Dali skrzętnie korzystam z jego pomocy przy utworzeniu encji.


, a następnie

Po chwili mam kompletną klasę encji, która jest o tyle ciekawa, że jak się później okazało wymagała adnotacji wcześniej przeze mnie nie wykorzystywanych - @NamedQuery oraz @Column z atrybutem columnDefinition. Użycie @NamedQuery było opcjonalne i służyło jedynie upraszczeniu zarządzania zapytaniem (możliwe późniejsze modyfikacje) podczas, gdy użycie z @Column(columnDefinition="NUMERIC(10)") było konieczne dla poprawnego działania TopLink z Firebird. TopLink jest domyślnym środowiskiem JPA w GlassFish i niestety nie posiada wsparcia dla Firebird, a domyślna konfiguracja TopLink wskazuje na Oracle i stąd pewne rozbieżności w tworzonych komendach SQL.

package pl.jaceklaskowski.jpa.firebird.entities;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;

@Entity
@NamedQuery(name = "znajdzWszystkichKlientow", query = "SELECT c FROM Customer c")
public class Customer {

private int pesel;

private String name;

public Customer() {
}

public Customer(int pesel, String name) {
this.pesel = pesel;
this.name = name;
}

@Id
@Column(columnDefinition = "NUMERIC(10)")
public int getPesel() {
return pesel;
}

public void setPesel(int pesel) {
this.pesel = pesel;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

Do pełnej konfiguracji encji Customer potrzebny był jeszcze plik konfiguracyjny - persistence.xml.

<?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="firebird">
<jta-data-source>jdbc/Firebird</jta-data-source>
<class>pl.jaceklaskowski.jpa.firebird.entities.Customer</class>
<properties>
<property name="toplink.ddl-generation" value="drop-and-create-tables"/>
<property name="toplink.logging.level" value="ALL"/>
</properties>
</persistence-unit>
</persistence>


Krok 5. Utworzenie aplikacji internetowej - firebird

Stworzyłem bardzo trywialny servlet, który podlega zarządzaniu zależnościami w Java EE 5 i mogłem skorzystać z adnotacji.

package pl.jaceklaskowski.jpa.firebird.servlets;

import java.io.IOException;
import java.util.List;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.UserTransaction;

import pl.jaceklaskowski.jpa.firebird.entities.Customer;

@SuppressWarnings("serial")
public class CreateCustomerServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {

@PersistenceContext(unitName = "firebird")
private EntityManager em;

@Resource
private UserTransaction ut;

@SuppressWarnings("unchecked")
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int pesel = Integer.parseInt(request.getParameter("pesel"));
String name = request.getParameter("name");
if (pesel > 0 && name != null) {
Customer customer = new Customer(pesel, name);
try {
ut.begin();
em.persist(customer);
ut.commit();
} catch (Exception e) {
throw new ServletException(e);
}
}
List<Customer> customers = em.createNamedQuery("znajdzWszystkichKlientow").getResultList();
request.setAttribute("customers", customers);
request.getRequestDispatcher("/customers.jsp").forward(request, response);
}
}

z poniższym deskryptorem instalacji aplikacji internetowej - WEB-INF/web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee web-app_2_5.xsd" version="2.5">
<display-name>firebird-web</display-name>
<servlet>
<description></description>
<display-name>CreateCustomerServlet</display-name>
<servlet-name>CreateCustomerServlet</servlet-name>
<servlet-class>pl.jaceklaskowski.jpa.firebird.servlets.CreateCustomerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CreateCustomerServlet</servlet-name>
<url-pattern>/createCustomer</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>customers.jsp</welcome-file>
</welcome-file-list>
</web-app>

Pozostało stworzyć stronę prezentującą wyniki działania aplikacji - customers.jsp:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Klienci</title>
</head>
<body>
<form action="createCustomer">
<table>
<tr>
<td>PESEL:</td>
<td><input name="pesel" /></td>
</tr>
<tr>
<td>Nazwa:</td>
<td><input name="name" /></td>
</tr>
<tr>
<td><input type="submit" value="Stwórz" /></td>
</tr>
</table>
</form>
<table border="0" cellpadding="1" cellspacing="1">
<tr>
<td>Lp.</td>
<td>PESEL</td>
<td>Nazwa</td>
</tr>
<c:forEach var="customer" items="${customers}" varStatus="status">
<tr>
<td>${status.count}</td>
<td>${customer.pesel}</td>
<td>${customer.name}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
Krok 6. Uruchomienie aplikacji

Wybieram menu kontekstowe Run As->Run on Server:


i po chwili w zakładce konsoli pojawiają się nieciekawe, ale oczekiwane komunikaty o uruchomieniu aplikacji:

Buildfile: C:\.eclipse\jpa\.metadata\.plugins\org.eclipse.jst.server.generic.core\serverdef\sunappsrv-ant.xml
deploy.j2ee.web:
[echo] C:/.eclipse/jpa/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/firebird-web
[jar] Building jar: C:\.eclipse\jpa\.metadata\.plugins\org.eclipse.jst.server.generic.core\serverdef\firebird-web.war
tools:
deploy:
[exec] Command deploy executed successfully.
deploy-url-message:
[echo] Application Deployed at: http://127.0.0.1:8080/firebird-web
BUILD SUCCESSFUL
Total time: 8 seconds


a w dzienniku zdarzeń GlassFish'a:

[#|2007-02-26T08:18:15.546+0100|INFO|sun-appserver9.1|oracle.toplink.essentials.session.file:/C:/apps/glassfish/domains/domain1/applications/j2ee-modules/firebird-web/WEB-INF/lib/f
irebird.jar-firebird|_ThreadID=14;_ThreadName=Thread-24;|Not able to detect platform for vendor name [Firebird 2.0*WI-V2.0.0.12748 Firebird 2.0/tcp (dev)/P10]. Defaulting to [oracl
e.toplink.essentials.platform.database.DatabasePlatform]. The database dialect used may not match with the database you are using. Please explicitly provide a platform using proper
ty toplink.platform.class.name.|#]

[#|2007-02-26T08:18:15.562+0100|CONFIG|sun-appserver9.1|oracle.toplink.essentials.session.file:/C:/apps/glassfish/domains/domain1/applications/j2ee-modules/firebird-web/WEB-INF/lib
/firebird.jar-firebird.connection|_ThreadID=14;_ThreadName=Thread-24;|connecting(DatabaseLogin(
platform=>DatabasePlatform
user name=> ""
connector=>JNDIConnector datasource name=>null
))|#]

[#|2007-02-26T08:18:15.562+0100|CONFIG|sun-appserver9.1|oracle.toplink.essentials.session.file:/C:/apps/glassfish/domains/domain1/applications/j2ee-modules/firebird-web/WEB-INF/lib
/firebird.jar-firebird.connection|_ThreadID=14;_ThreadName=Thread-24;|Connected: jdbc:firebirdsql:java://localhost/glassfish-jpa
User: sysdba
Database: Firebird 2.0*WI-V2.0.0.12748 Firebird 2.0/tcp (dev)/P10 Version: WI-V2.0.0.12748 Firebird 2.0*WI-V2.0.0.12748 Firebird 2.0/tcp (dev)/P10
Driver: Jaybird JCA/JDBC driver Version: 2.1|#]

[#|2007-02-26T08:18:15.562+0100|CONFIG|sun-appserver9.1|oracle.toplink.essentials.session.file:/C:/apps/glassfish/domains/domain1/applications/j2ee-modules/firebird-web/WEB-INF/lib
/firebird.jar-firebird.connection|_ThreadID=14;_ThreadName=Thread-24;|connecting(DatabaseLogin(
platform=>DatabasePlatform
user name=> ""
connector=>JNDIConnector datasource name=>null
))|#]

[#|2007-02-26T08:18:15.562+0100|CONFIG|sun-appserver9.1|oracle.toplink.essentials.session.file:/C:/apps/glassfish/domains/domain1/applications/j2ee-modules/firebird-web/WEB-INF/lib
/firebird.jar-firebird.connection|_ThreadID=14;_ThreadName=Thread-24;|Connected: jdbc:firebirdsql:java://localhost/glassfish-jpa
User: sysdba
Database: Firebird 2.0*WI-V2.0.0.12748 Firebird 2.0/tcp (dev)/P10 Version: WI-V2.0.0.12748 Firebird 2.0*WI-V2.0.0.12748 Firebird 2.0/tcp (dev)/P10
Driver: Jaybird JCA/JDBC driver Version: 2.1|#]

, czyli działa! A zatem na dzisiaj koniec.

Problemy

Najczęstszy to brak pamięci, który pojawia się pracując z plikami XML w tak "rozszerzonym" Eclipse IDE. Być może zwiększenie pamięci naprawiłoby problem, ale nie chciało mi się szukać.

I drugi, równie bolesny, to utrata stanu GlassFish przez wtyczkę GlassFish Eclipse plugin, kiedy w trakcie zatrzymywania serwera próbowałem uruchomić go ponownie. Rozwiązanie okazało się wymagające monitorowania stanu GF - podgląd dziennika zdarzeń GF ($GLASSFISH_HOME/domains/domain1/logs/server.log). Możemy uruchomić GF dopiero po pojawieniu się następujących wpisów w dzienniku zdarzeń:

[#|2007-02-26T08:19:39.796+0100|INFO|sun-appserver9.1|javax.enterprise.system.core|_ThreadID=15;_ThreadName=RMI TCP Connection(6)-127.0.0.1;|CORE5052: Application shutdown complete
.|#]

[#|2007-02-26T08:19:40.109+0100|INFO|sun-appserver9.1|javax.enterprise.system.tools.deployment|_ThreadID=15;_ThreadName=RMI TCP Connection(6)-127.0.0.1;|[AutoDeploy] Disabling Auto
Deployment service.|#]

Wcześniejsza próba uruchomienia będzie objawiała się tym, że Eclipse będzie raportował o próbie uruchomienia GF (Status=Starting...)


mimo, że tak na prawdę nie kontroluje GF i komunikat będzie trwał i trwał. Jedynym wyjściem wydaje się być zatrzymanie serwera z poziomu Eclipse IDE (tj. wciśnięcie ikony w postaci czerwonego kwadratu) i ponowne jego uruchomienie.

Czasami podczas uaktualniania aplikacji (ponownej jej instalacji na serwerze) pojawiał się błąd:

Buildfile: C:\.eclipse\jpa\.metadata\.plugins\org.eclipse.jst.server.generic.core\serverdef\sunappsrv-ant.xml
deploy.j2ee.web:
[echo] C:/.eclipse/jpa/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/firebird-web
[jar] Building jar: C:\.eclipse\jpa\.metadata\.plugins\org.eclipse.jst.server.generic.core\serverdef\firebird-web.war
tools:
deploy:
[exec] Command deploy executed successfully with following warning messages: WARNING:
[exec] The following file(s) were left(locked) from the previous deployment; they were not updated during this deployment and could interfere with the application:
[exec] C:\apps\glassfish\domains\domain1\applications\j2ee-modules\firebird-web\WEB-INF\lib\firebird.jar
deploy-url-message:
[echo] Application Deployed at: http://127.0.0.1:8080/firebird-web
BUILD SUCCESSFUL
Total time: 4 seconds

co obchodzimy przez zatrzymanie GF i ponowne uruchomienie.

Skłaniam się ku stwierdzeniu, że najmniej uciążliwym sposobem uaktualnienia aplikacji na serwerze GlassFish z użyciem wtyczki GlassFish Eclipse plugin jest po prostu zatrzymywanie serwera i jej instalacja za pomocą menu kontekstowego Publish (co automatycznie uruchomi również serwer, jeśli zatrzymany).

25 lutego 2007

Eclipse Mylar - JIRA, Bugzilla i Trac w Eclipse 3.3

7 komentarzy
Ale się ucieszyłem! W końcu jest! Eclipse Mylar 2.0M1 działa w mojej instalacji Eclipse IDE 3.3m5eh. Walczyłem z tym przez ponad 2 tygodnie (wcześniej z poprzednimi wersjami 3.3) szukając rozwiązania po wszystkich forach, systemach zarządzania zgłoszeniami, próbując różnych konfiguracji Eclipse IDE oraz wtyczek, itp. Byłem już bliski śledzenia wykonywania (ang. debugging) wtyczki, aż wczoraj, ot tak przypadkiem, bliski zrezygnowania z Mylar, zauważyłem, że Eclipse korzysta z pliku jvm.dll z katalogu C:/Program Files/Java/jre1.4.2_13 (można to zobaczyć w Help->About Eclipse SDK->Configuration Details, a tam parametr -vm, który teraz po zmianie wskazuje na C:\apps\jre5\bin\client\jvm.dll).

Jeszcze w sobotę, znalazłem informację o wymaganiach Eclipse Dali (inna wtyczka, która nie chce współpracować), gdzie wyraźnie zaznaczono, że wymagane jest uruchomienie Eclipse z Java 5, aby funkcjonalność JPA działała poprawnie (czytaj: w ogóle była dostępna). Ta informacja, problemy z Mylar i fakt, że w zasadzie Java 1.4.2 nie była mi już potrzebna, sprawiły, że zdecydowałem się usunąć Java 1.4.2 całkowicie.

Jakież było moje zdumienie, kiedy po ponownym uruchomieniu komputera i uruchomieniu Eclipse zobaczyłem Help->Mylar UI Legend


, czy Window->Open Perspective->Other...->Planning


, nowe widoki w kategorii Mylar i Mylar Sandbox


, czy w końcu nowa ikona w widoku Package Explorer.


A skąd tyle zainteresowania i uporu w uruchomieniu wtyczki Mylar?! To wiedziałem jeszcze zanim działała mi wtyczka. Zarządzanie zgłoszeniami w dowolnym systemie zgłoszeń (JIRA, BugZilla, czy Trac) to praca z przeglądarką - wyszukiwanie zgłoszenia, pobranie poprawki (łaty, ang. patch), jeśli dostępna, dołączenie do projektu, kompilacja, nierzadko poprawa samej poprawki, zmiana stanu zgłoszenia, tworzenie poprawki, itp. Gdybym mógł zarządzać zgłoszeniami z poziomu środowiska IDE (przy projektach Apache Geronimo i Apache OpenEJB jest to JIRA) zdecydowanie ułatwiłoby mi to pracę (i być może znalazłbym go więcej dla nich ;-)). Po przejrzeniu prezentacji na temat Mylar - Webinar: Task-Focused Programming with Mylar i poznaniu oferowanych możliwości, np. możliwość włączenia poprawki do projektu z poziomu Eclipse, nie mogę uwierzyć, że tak mało pisze się o nim. Przecież to powinna być wtyczka na stanie każdego programisty korzystającego z Eclipse IDE (!) Poza wtyczkami WTP, TestNG, Subclipse/Subversive, Spring IDE, JBoss IDE, Apache Geronimo, GlassFish, Groovy i zapewne kilku innych, wtyczka Eclipse Mylar stała się dla mnie numerem 1. Po wydaniu nowej wersji rozwojowej WTP (z Eclipse Dali jako narzędzie JPA) praca z Eclipse staje się niezwykle ciekawa i prostsza (myli się jednak ten, kto myśli, że NetBeans IDE jest daleko w tyle! Ostatnia wersja NetBeans IDE 6.0 m7 to...może następnym razem. Pora na Mylar!).

23 lutego 2007

Pakunki OSGi w projekcie wielomodułowym Apache Maven 2 z maven-bundle-plugin

0 komentarzy
Pojawił się 5. artykuł w serii Getting started with OSGi autorstwa Neila Bartletta - Getting Started with OSGi: Consuming a Service - i podobnie, jak poprzednio opisywałem w Tworzenie pakietów OSGi z Apache Maven 2 zainspirowany OSGi, postanowiłem jeszcze bardziej urozmaicić lekturę artykułów Neila i zaprezentować kilka ciekawych usprawnień. Napisałem kolejny artykuł o OSGi, Apache Maven 2 i Eclipse IDE.

Tym razem zaprezentuję możliwość zarządzania wieloma modułami przez M2 (projekt wielomodułowy) oraz wtyczkę Bundle Plugin for Maven dalej zwaną maven-bundle-plugin. Pozwoli to na kolejne uproszczenia w zarządzaniu projektem z wieloma pakunkami OSGi (ang. OSGi bundles). Już w przypadku 2 pakunków manualne zarządzanie nimi sprawia pewne trudności, a przy ich większej ilości jest bolesne. Pamiętanie wszystkich poleceń do kompilacji i utworzenia pakunków możemy zlecić M2, szczególnie, że jego obecność w projekcie dostarcza nam innych możliwości bez dodatkowych nakładów pracy (zarządzanie zależnościami, tworzenie dokumentacji, automatyczne wykonywanie testów, itp.). Wdrożenie wtyczki Bundle Plugin for Maven to pozbycie się ręcznego zarządzania tworzeniem pliku MANIFEST.MF (ech, byłoby jeszcze przyjemniej, gdyby OSGi wspierało adnotacje). Stworzymy pojedyńczą definicję MANIFEST.MF (jako konfigurację wtyczki maven-bundle-plugin) w projekcie macierzystym i wszystkie, zarządzane przez niego projekty będą ją dziedziczyły. Poza tym, skorzystamy również z biblioteki OSGi - org.osgi.core - dostarczanej przez projekt Apache Felix i zlecimy M2 jej pobranie.

Więcej w artykule Pakunki OSGi w projekcie wielomodułowym Apache Maven 2 z maven-bundle-plugin. Miłej lektury!

Weekend przede mną! Czas na odpoczynek i coś bardziej przyziemnego jak dokończenie lektury...specyfikacji JPA 1.0 ;-) Wydaje się, że najwyższa pora na jakieś przykłady. Rozgrzany ostatnimi dokonaniami z M2 wierzę, że nie powinienem się zbytnio trudzić - akurat na weekend!

19 lutego 2007

Tworzenie pakietów OSGi z Apache Maven 2

5 komentarzy
Rozczytując się w artykułach dotyczących OSGi z serii Getting Started with OSGi postanowiłem je trochę usprawnić i tym samym zachęcić większą ilość osób do popróbowania się z tematem. Postanowiłem spróbować sił tworzenia pakietów OSGi z Apache Maven 2.0.5 i Eclipse IDE 3.3M5, aby zautomatyzować prace przy projekcie. Podekscytowany łatwością pracy z OSGi, jedyną sprawą do rozwiązania było zautomatyzowanie kompilacji, budowania i instalacji. Brzmi znajomo?! To właśnie dokładnie te zadania, dla których powstał M2 (tworzenie klas pozostawiłem Eclipse'owi). W artykule zakłada się ręczne wykonywanie tych kroków, a kto ma czas na pisanie w notatniku i kompilację z linii poleceń?! Wymagało to trochę usprawnień - wprowadzenie M2.

Okazało się, że było niezwykle prosto i przyjemnie i w dodatku powstał nowy artykuł Tworzenie pakietów OSGi z Apache Maven 2. Zachęcam do lektury i nadsyłania komentarzy.

Pora wracać do specyfikacji JPA. Chwila, przecież jutro spotkanie Warszawa JUG i prezentacja JBoss Seam! Sprawdźmy jeszcze raz jak wygląda prezentacja i przykłady. Do zobaczenia na spotkaniu!

Coraz bardziej urzeczony OSGi - nowe artykuły na JavaLobby

2 komentarzy
Pojawiły się kolejne mini-artykuły dotyczące OSGi z serii Getting started with OSGi. Coraz bardziej urzekają mnie powody stworzenia OSGi oraz możliwości płynące z wykorzystania platformy w modularnych aplikacjach. Po kilku dniach lektury dokumentacji i przeglądania różnych serwisów internetowych dotykających tematu OSGi, pokuszę się na przybliżenie (i możliwe zainteresowanie) tą technologią.

OSGi jest platformą komponentów nazywanych pakietami OSGi (ang. OSGi bundle - tłumaczenie zapożyczone ze Słownika informatycznego angielsko-polskiego IDG.pl). Idea polega na stworzeniu platformy, w której pakiety podlegają pewnym transformacjom, w sensie rozwoju. Wyróżnione są stadia (stany) rozwojowe w cyklu życiowym komponentu (org.osgi.framework.Bundle):
  • UNINSTALLED
  • INSTALLED
  • RESOLVED
  • STARTING
  • STOPPING
  • ACTIVE
Wydaje się, że takich podobnie funkcjonujących szkieletów jest kilka (PicoContainer, HiveMind, Spring Framework, Apache XBean, NanoContainer, Plexus, jądra serwerów aplikacyjnych, Apache Maven 2 z mechanizmem wtyczek, czy wręcz EJB 3.0 i JSF 1.2). Możnaby tak wymieniać, ale przy OSGi na uwagę zasługują następujące fakty:
Poza stadiami wyróżnia się również sposób zarządzania zależnościami (brzmi jakby znajomo z Apache Maven 2, nieprawdaż?). Innymi słowy, podobnie jak w Maven 2 czy w kontenerach IoC - Spring Framework, na przykład - możemy deklarować zależności, które są rozwiązywane przez platformę OSGi. Co więcej, jak przeczytałem w Getting Started with OSGi: Registering a Service, w przeciwieństwie do wymienionych rozwiązań, OSGi pozwala na, tzw. dynamiczne IoC, tj. zależności (pakiety OSGi) można dynamicznie podmieniać (ang. hot-swap). Raz uaktywniony pakiet można dostarczyć pakietom zależnym, a po chwili podmienić jego implementację (!)

Z mojej perspektywy wydaje się, że rozwiązania modularne promowane przez otwarte projekty są bardziej znane niż implementacje OSGi. Kto słyszał o OSGi? Może jakieś wzmianki, ale korzystać z tego? Nie sądzę.

Dostępnych, darmowych implementacji OSGi R4 jest kilka:
Seria mini-artykułów, a właściwie mini-przepisów, Getting started with OSGi przyjemnie wprowadza w temat. Może kogoś jeszcze zainteresuje?!

18 lutego 2007

V spotkanie Warszawskiej Grupy Użytkowników Technologii Java (Warszawa JUG)

0 komentarzy
Warszawska Grupa Użytkowników Technologii Java (Warszawa JUG) zaprasza na V spotkanie, które odbędzie się w nadchodzący wtorek 20.02.2007 o godzinie 18:00 w sali 4420 na Wydziale MiMUW przy ul. Banacha 2 w Warszawie.

Temat prezentacji: Wprowadzenie do JBoss Seam

JBoss Seam jest szkieletem programistycznym do tworzenia aplikacji z wykorzystaniem technologii Java EE 5. Idealnie pasuje do miana Ruby on Rails dla platformy Java EE. Pozwala na wykorzystanie wielu ciekawych, gotowych rozwiązań JSF (ICEFaces, facelets, Tomahawk) oraz EJB 3.0 bez większego przygotowania teoretycznego. Wystarczy podstawowa znajomość Java SE 5 (adnotacje) oraz JSF i już można konstruować zaawansowane aplikacje. Dodatkowa wiedza o tworzeniu aplikacji przemysłowych w Java EE 5 podnosi wartość wykorzystania JBoss Seam i jego udogodnień. Jeśli w zasięgu Twoich zainteresowań jest tworzenie aplikacji internetowych koniecznie musisz poznać JBoss Seam! Z pewnością każdy programista Java znajdzie coś ciekawego dla siebie.

Prezentacja prowadzona będzie przez Jacka Laskowskiego, który jest członkiem grup rozwojowych projektów Apache Geronimo i Apache OpenEJB. Jest założycielem i liderem Warszawskiej Grupy Użytkowników Technologii Java (Warszawa JUG). Prowadzi Notatnik Projektanta Java EE, w którym znaleźć można m.in. relacje z poznawania technologii Java EE 5.

Planowany czas prezentacji to 1,5 godziny (wliczając około 15-minutową dyskuję).

Na spotkaniu poruszony będzie również temat organizacji konferencji Java w Warszawie organizowanej przez Warszawa JUG.

Zapraszam w imieniu Warszawa-JUG!

Krótki film o Groovy 1.0 w Eclipse IDE 3.3M5

3 komentarzy
W jedyn z katalogów znalazłem kilka plików o nazwach rozpoczynających się cyframi. Nie wiem dlaczego, ale właśnie dzisiaj nie przypadły mi do gustu i zdecydowałem, że je zmienię. Tylko jak? Na pewno konieczne było napisanie jakiegoś skryptu. Przypomniałem sobie o Groovy!

Groovy to jeden z wielu języków skryptowych w Javie, którego poznanie zawsze odkładałem. Po ciekawych i bardzo produktywnych doświadczeniach z Jython nadeszła pora na Groovy. Nie zamierzałem jednak tworzyć skryptów ręcznie. Ostatnio uaktualniałem Eclipse IDE do wersji 3.3M5 i na niego padło jako środowisko, w którym będę tworzył skrypty Groovy (w odwodzie oczekiwał niemile zaskoczony NetBeans IDE 5.5.1).

Rozochocony pomysłem postanowiłem jeszcze bardziej go uatrakcyjnić i...nakręcić film! Pora popróbować się z Wink. Pomysł jego wykorzystania pojawił się właściwie kilka dni temu, kiedy przeglądając jeden z blogów natrafiłem na filmy Winka. Krótkie acz treściwe filmiki o danej funkcjonalności znacznie uatrakcyjniały prezentowany słownie projekt, czy jego wybraną cechę. Pomyślałem dlaczego by nie wykorzystać go u mnie i tym samym postanowiłem wprowadzić Winka do narzędzi codziennego użytku. Inicjacja moich poczynań z Winkiem wymagała czegoś ekstra, a rozpoznawanie Groovy w Eclipse to był temat wręcz wymarzony.

Po kilku godzinach stworzyłem krótki film o instalacji wtyczki Groovy Eclipse Plugin, tworzeniu skryptu Groovy i ostatecznie jego uruchomieniu. Filmowanie nie jest takie proste, ale warto było, bo ostateczny efekt okazał się ciekawszy niż początkowo sądziłem.


A o możliwościach Groovy nie wspomnę. Nieźle się gimnastykowałem z domknięciami (ang. closures). Ich prostota mnie wręcz wykańczała. Wymagają one zupełnie innego myślenia o strukturze tworzonego programu. Mówi się, że raz poznawszy domknięcia, programista nie może bez nich żyć. Brrr, mnie to także czeka?!

Pierwszy skrypt w Groovy służy do zmiany nazwy plików w katalogach podanych na linii poleceń. Jego uruchomienie przedstawiam w filmie.

package pl.jaceklaskowski.groovy.renamer;

class Main {

static void main(args) {
println "Huuraa! Rozpoczynamy zabawę z Groovy!"
args.each { d ->
println "Katalog: $d"
def p = '^[0-9]+[-_]?'
new File(d).eachFileMatch(~"$p.*") { f ->
def newFileName = "${f.getName() - p}"
print " ${f.getName()} staje się $newFileName..."
f.renameTo(new File(d + '/' + newFileName))
println "zrobione"
}
}
}

}

Bardzo ciekawa jest obsługa sytuacji brzegowych - brak podania parametrów wejściowych na linii poleceń, wyrażenia regularne i same domknięcia. Starałem się ująć w skrypcie jak najwięcej ciekawych sztuczek Groovy, aby poczuć jego klimaty. Podsumowanie: Groovy podoba mi się!

15 lutego 2007

Tworzenie aplikacji z JBoss Seam 1.1.6 w 5 minut!

2 komentarzy
W dzisiejszym biuletynie Builder.com.au pojawiło się niezwykle trafne stwierdzenie autorstwa Chrisa Ducketta, które odzwierciedla naturę wielu projektantów i programistów:

Perhaps deep inside many developers and tech enthusiasts there is a little sadist that enjoys frivolous installations. Or maybe the attention span of those developers is so low they constantly need to be entertained by new things as they remain on the bleeding edge.

Zastanawiam się, kto z nas nie zgodziłby się z tym?! Ja nie!

Dla osób będących w ciągłym niedoczasie, a pragnących spróbować sił z JBoss Seam - podaję recepturę na działającą aplikację seamową w 5 minut (z dokładnością do czasu pobrania oprogramowania)! Jest to ten wycinek dokumentacji JBoss Seam, który pozwala w ekspresowym tempie potraktować Seama jako odpowiednik Ruby on Rails w świecie Java EE.

Podjąłem się wyzwania, aby to sprawdzić i nie tylko, że się da, ale i można pracować z linii poleceń! Włączam stoper.
  1. Rozpoczynam od pobrania JEMS Installer 1.2.0.GA.
    Alternatywnie można i tak:
    wget http://easynews.dl.sourceforge.net/sourceforge/jboss/jems-installer-1.2.0.GA.jar?download -O jems-installer-1.2.0.GA.jar
  2. Instaluję JBoss AS 4.0.5.GA z EJB 3.0 w katalogu c:/jboss-4.0.5.GA_ejb3.
    java -jar ./jems-installer-1.2.0.GA.jar -installGroup ejb3 installpath=c:/jboss-4.0.5.GA_ejb3
  3. Pobieram JBoss Seam 1.1.6.GA.
    Korzystam z przeglądarki, ale można i tak:
    wget http://downloads.sourceforge.net/jboss/jboss-seam-1.1.6.GA.zip?modtime=1170959601&big_mirror=1 -O jboss-seam-1.1.6.GA.zip
  4. Instaluję JBoss Seam, co sprowadza się do rozpakowania paczki do wybranego katalogu, np. c:/jboss-seam-1.1.6.GA.
    jar -xvf jboss-seam-1.1.6.GA.zip
  5. Konfiguruję środowisko dla seam-gen, tj. wykonuję polecenia seam setup z katalogu domowego JBoss Seam (u mnie c:/jboss-seam-1.1.6.GA).
    Uwaga dla działających na MS Windows z Cygwin: uruchamiam polecenie z ustawioną zmienną JAVA_HOME zgodnie z konwencją w MS Windows, np. JAVA_HOME="c:/apps/java5" ./seam setup).

    Przy drugim pytaniu podajemy katalog domowy JBoss AS, np. c:/jboss-4.0.5.GA_ejb3. Dla pozostałych pytań akceptujemy wartości domyślne (wciśnięcie klawisza ENTER).
  6. Tworzę nowy projekt seamowy z pomocą polecenia seam new-project. Uwaga dotycząca wydawania poleceń pod MS Windows nadal w mocy, tj. JAVA_HOME="c:/apps/java5" ./seam new-project.
  7. Uruchamiam JBoss AS z poziomu innej konsoli
    cd c:/jboss-4.0.5.GA_ejb3; ./bin/run.sh
  8. Uruchamiam aplikację na JBoss AS za pomocą seam explode. I jak wcześniej, uwaga dla użytkowników MS Windows i Cygwin - JAVA_HOME="c:/apps/java5" ./seam explode.
    Na konsoli JBoss AS powinny pojawić się komunikaty informujące o instalacji.
  9. Otwieram stronę http://localhost:8080/myproject.
  10. Koniec!
Dla posiadających więcej niż 5 minut zapraszam na strony dokumentacji JBoss Seam 1.1.6.GA.

OSGi, uaktualnienie narzędzi - Eclipse 3.3M5 i Maven 2.0.5 - i nadchodzące spotkanie Warszawa JUG o JBoss Seam

0 komentarzy
Kilka spraw jednocześnie, ale inaczej się nie da. Poszukując błędu w jednym z projektów chwilowo porzuciłem lekturę specyfikacji JPA. Powracam do niej od czasu do czasu, ale bez większego zaangażowania, aby wszystkie nowinki spisać. Czuję jednak, że dzisiaj/jutro powrócę do tematu relacji lektury specyfikacji JPA. Tak mi podpowiada zdrowy rozsądek, aby zakończyć JPA i podejść do innych zajęć, ale rozsądek rozsądkiem, a życie przynosi czasami zaskakujące propozycje alternatywne ;-)

I nie inaczej było i tym razem. W moim niewielkim świecie tworzenia oprogramowania jest kilka projektów i technologii, które są na niekończącej się liście do ewaluacji/poznania. Lista ciągle podlega zmianom, w zależności od nastroju i ogólnego klimatu wokół niej.

Jedną z technologii, która od dłuższego czasu mnie nurtuje jest OSGi (http://www.osgi.org). W skrócie, OSGi jest specyfikacją platformy usług nadzorowaną przez OSGi Alliance. Moja dotychczasowa aktywność w tej technologii sprowadzała się do śledzenia poczynań projektów Apache Felix oraz Eclipse Equinox, które są dwoma głównymi projektami otwartymi realizującymi specyfikację OSGi R4 (do pełnej oferty należy dodać jeszcze projekt Knoplerfish). Trwa to już jakiś czas i do tej pory wystarczyło, aby upewnić się, że jest to technologia, którą należy konieczne rozpoznać. W końcu nadszedł ten dzień, a wszystko za sprawą artykułu na JavaLobby - Getting started with OSGi: Your first bundle. Nie mogłem wymarzyć sobie lepszego artykułu do rozpoczęcia poznawania specyfikacji OSGi! Jest niezwykle krótki, acz treściwy i każdemu nowicjuszowi OSGi go polecam na rozgrzewkę. Wychodzę z założenia, że czym więcej osób interesuje się danym tematem tym większa szansa na jego dokładniejsze zbadanie (najczęściej poprzez wymianę doświadczeń) i dla zainteresowania dodam, że OSGi jest platformą dla produktów Eclipse IDE oraz IBM WebSphere Application Server 6.1, a ostatnio zainteresowali się tym również programiści Spring Framework ustanawiając projekt Spring-OSGi. Wciąż to powtarzam, ale będzie to inauguracja w Notatniku - jeśli rok 2006 był rokiem projektów opartych o Spring Framework i Hibernate, a 2007 będzie rokiem powrotu do platformy Java EE 5, to rok 2008 będzie z pewnością rokiem projektów OSGi. Jest to kolejne uproszczenie przy tworzeniu rozwiązań modularnych, a specyfikacje JSR 277: Java Module System oraz JSR 291: Dynamic Component Support for Java SE są kolejnym dowodem, że istnieje duże zapotrzebowanie na takie rozwiązania.

Poza OSGi, na uwagę zasługuje pojawienie się kolejnych wersji projektów codziennego użytku - Eclipse IDE 3.3 M5 oraz Apache Maven 2.0.5. Szczególnie Maven2 był wielce oczekiwany i coraz częstsze błędy pojawiające się w obszarze zarządzania zależnościami powodowały u wielu zwątpienie, czy jest to wystarczająco dojrzałe rozwiązanie do obsługi rozbudowanych projektów, jak np. Apache Geronimo. Eclipse IDE nie imponuje swoim rozmachem w tej wersji, ale pojawiło się kolejnych kilka dodatków, jak formatowanie kodu przy zapisie per projekt, które warte są zainteresowania.

I na koniec, zajmujący mnie ostatnio projekt - JBoss Seam, czyli temat kolejnego spotkania Warszawa JUG w nadchodzący wtorek. Muszę przyznać, że warto było zapoznać się najpierw ze specyfikacjami EJB3 (w tym i JPA 1.0) oraz JSF 1.2, przed przystąpieniem do Seama. Znacznie upraszcza to zrozumienie zaproponowanych w nim rozwiązań. Dodać do tego należy możliwość skorzystania z facelets, ICEFaces, TestNG, Trinidad, ajax4jsf i mamy ciekawą mieszankę projektów w jednym. Na pewno nie można się z nim nudzić. Istnieje możliwość uruchomienia Seama na JBoss AS 4 oraz GlassFish, a w ramach dokształcenia postanowiłem spróbować uruchomienia na Apache Geronimo. Niestety pierwsza próba zakończyła się niepowodzeniem - brak wsparcia dla klienta aplikacyjnego Java EE w Geronimo. Zgłosiłem błąd (GERONIMO-2827) i postanowiłem przyjrzeć się bliżej tematowi. Z pewnością nie uda mi się go poprawić do spotkania, ale może zaraz po nim?! A skoro wróciłem do tematu spotkania, to zastanawiam się, co z tak rozległego projektu jakim jest JBoss Seam należałoby zaprezentować w 1,5 godziny?! Wydaje się, że na jednym spotkaniu o Seamie się nie skończy.

10 lutego 2007

requiredMessage i resource-bundle - udoskonalona kontrola komunikatów w JSF 1.2

3 komentarzy
Co nie dotknę, to coś nowego. Po prostu strach cokolwiek dotykać! ;-)

Po przeczytaniu sekcji 3.5 Encyjni obserwatorzy i metody przechwytujące specyfikacji JPA postanowiłem przygotować aplikację internetową, która pomogłaby mi utrwalić przeczytany materiał. Skorzystałem z pomocy NetBeans IDE 5.5.1 i GlassFish v2.

Rozpocząłem od stworzenia projektu i zabrałem się za tworzenie stron JSF. Pierwsza strona umożliwiała podanie nazwy użytkownika w polu h:inputText. Poszło gładko. Zdefiniowałem komponent zarządzany user, który miał przechowywać informację o wprowadzonym identyfikatorze użytkownika. Nie korzystałem z NetBeans Visual Web Pack 5.5, więc wszystko robiłem ręcznie, aż w pewnym momencie, edytując stworzony faces-config.xml dostrzegłem, że jestem w JSF 1.2 (!) Pomyślałem, że jest to dobra pora, aby zapoznać się z kilkoma dobrodziejstwami JSF 1.2, co zaraz przerodziło się w wykorzystanie gdzie się dało Unified EL, tj. tam, gdzie w poprzedniej wersji należało skorzystać z h:outputText teraz można zastąpić prostszymi konstrukcjami znanymi z JSP i JSTL, czyli ${nazwaKomponentuZarządzanego.atrybut}. Kiedy przyszło do przypisania komponentu zarządzanego user do h:inputText zauważyłem atrybut, którego z pewnością nie było w poprzedniej wersji JSF 1.1 - requiredMessage.

Zmiana komunikatów w JSF 1.1 związanych z obsługą błędów kontroli poprawności wprowadzonych danych nie była ani łatwa, ani zaawansowana (aż trudno uwierzyć, że twórcom JSF udało się to połączyć, zazwyczaj jest albo jedno, albo drugie). Zmiana komunikatów błędów polegała na dostarczeniu pliku komunikatów z odpowiednimi identyfikatorami, których wartości były zmodyfikowanymi komunikatami (w tym i tłumaczeniami). Bardziej zaawansowane modyfikacje komunikatów błędów wymagały zastosowania pewnych sztuczek (więcej w artykule Hansa Bergstena Designing and Implementing Web Application Interfaces).

W JSF 1.2 udostępniono atrybut requiredMessage. Wartość atrybutu requiredMessage jest komunikatem błędu, który będzie wyświetlany w przypadku niepodania przez użytkownika obowiązkowej wartości w polu typu UIInput (wymaganie nałożone przez zastosowanie atrybutu required dla kontrolek h:inputText, h:inputSecret, h:inputHidden i h:inputTextArea), bądź identyfikatorem komunikatu w aplikacyjnym pliku komunikatów. Komunikat (egzemplarz klasy FacesMessage) będzie umieszczony w FacesContext zamiast domyślnego.

<h:inputText id="name" value="#{user.name}" required="true" requiredMessage="Nie podano nazwy użytkownika" />

W powyższym przykładzie, jeśli użytkownik nie poda nazwy użytkownika wyświetlony zostanie komunikat Nie podano nazwy użytkownika (oczywiście samo wyświetlenie można zrealizować z pomocą h:message albo h:messages).

Na uwagę zasługuje jednak inna cecha requiredMessage - możliwość wskazania na właściwy komunikat w pliku komunikatów.

<h:inputText id="name" value="#{user.name}" required="true" requiredMessage="#{validationMessages.usernameMissing}" />

W ten sposób, w zależności od aktywnego ustawienia regionalnego (ang. locale), wyświetlony zostanie właściwy komunikat błędu w aktywnym języku. Ale co to jest validationMessages?

Poniżej znajduje się wycinek pliku konfiguracyjnego JSF - faces-config.xml:

<application>
<resource-bundle>
<base-name>pl.jaceklaskowski.ewt.faces.ValidationMessages</base-name>
<var>validationMessages</var>
</resource-bundle>
</application>
Zmienna validationMessages reprezentuje mapę komunikatów (z kluczami będącymi identyfikatorami komunikatów), a pl.jaceklaskowski.ewt.faces.ValidationMessages jest nazwą pliku komunikatów bez rozszerzenia .properties (w formacie java.util.Properties) o zawartości:

usernameMissing=Nie podano nazwy użytkownika!

Specyfikacja JSF 1.2 opisuje atrybut jako współpracujący z f:loadBundle, jednakże nie udało mi się tego powiązać. W dodatku znalazłem wiadomość na forum JavaServer Faces, która wskazywałaby, że taka możliwość nie ma prawa działać - requiredMessage and EL?.

Poza tym podobną funkcjonalność można wykorzystać do komunikatów związanych z mechanizmem konwersji i kontroli poprawności (walidacji) za pomocą atrybutów converterMessage oraz validatorMessage, odpowiednio.

Dobre wprowadzenie do zmian w JSF 1.2 znajduje się w artykule Web Tier to Go With Java EE 5: Summary of New Features in JavaServer Faces 1.2 Technology. Na lekturę specyfikacji JSF 1.2 przyjdzie jeszcze pora ;-)

Ciekawostki języka Java

9 komentarzy
Pracuję z Javą od pierwszych dni jej opublikowania. Ostatnio zdobyłem certyfikat SCJP 5.0 i wydawałoby się, że to wystarczy do zagwarantowania dogłębnej znajomości języka. Ale jak pokazały wydarzenia dnia wczorajszego, niekoniecznie.

Pierwsza ciekawostka dotyczyła dostępu do zmiennych i metod prywatnych (składowych klasy oznaczonych słowem kluczowym private). Skoro prywatnych to wydawałoby się, że sprawa jasna - nikt poza ich klasą macierzystą nie ma prawa ich "dotknąć". Ale, jak się okazało, nie jest to precyzyjna definicja.

Możnaby zadać pytanie, jak bardzo utrzymywana jest prywatność składowych klasy (pól i metod)? Nie sądzę, aby nie znalazła się przynajmniej jedna osoba programująca na codzień w Javie, która nie złapałaby się za głowę, kiedy ujrzałaby działanie poniższego programu. Czy zapytana, potrafiłba odpowiedzieć jaki będzie wynik kompilacji i przyjmując, że kompilacja się zakończy poprawnie, jaki będzie wynik działania poniższego programu? Uwagę powinien przyciągnąć dostęp do prywatnego pola i metody w metodzie statycznej main.
  package accesscontrol;

public class Private {

private int privateMember;

public Private(int privateMember) {
this.privateMember = privateMember;
}

public static void modifyPrivateField(Private p) {
p.privateMember *= 2;
}

private void callPrivateMethod() {
System.out.println("Prywatna metoda wywołana! Na pewno?");
}

public static void main(String[] args) {
Private p = new Private(5);
System.out.println("p.privateMember (przed zmianą) = " + p.privateMember);
modifyPrivateField(p);
System.out.println("p.privateMember (po zmianie) = " + p.privateMember);
p.callPrivateMethod();
}
}
Spróbuj odpowiedzieć na pytania samodzielnie, zanim podam trochę dodatkowych informacji.

Skoro prywatne składowe klasy, to prywatne, tak? Otóż nie! Wszystko zależy od tego, kto dostępuje składowych prywatnych klasy. W Javie (mówi się, że podobnie jak w C++, ale przeciwnie do Smalltalk) dostęp do zmiennych prywatnych jest niemożliwy, za wyjątkiem sytuacji, w której wykonywane operacje są inicjowane przez klasę macierzystą. Dodać do tego należy, że dostęp do prywatnej składowej dowolnego egzemplarza klasy X wewnątrz metod klasy X jest również możliwy (!) Dla mnie taki dostęp był dostępem z zewnątrz, pomimo, że w klasie deklarującej ową prywatną składową.

Wytłumaczono mi to w ten sposób, że prywatne elementy służą ukrywaniu implementacji, wewnętrznej realizacji działania klasy i to ona sama podejmuje decyzje odnośnie prywatnych składowych. Może zatem podjąć decyzję o modyfikacji swoich własnych prywatnych składowych,w tym i dowolnego egzemplarza siebie samej.

Cała literatura nt. temat brzmi w takim stylu (wycinek z podręcznika Javy - The Java Tutorial sekcja Controlling Access to Members of a Class):

The private modifier specifies that the member can only be accessed in its own class.

Jak widać z zaprezentowanego przykładu, interpretacja powyższego wycinka może nie być satysfakcjonująca. Dla mnie nie była. Na szczęście specyfikacja języka Java - The Java Language Specification - w sekcji 6.6.1 Determining Accessibility zawiera precyzyjne wytłumaczenie:

If the member or constructor is declared private, then access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.

Może ranga dokumentu przekonuje mnie do tego wytłumaczenia, albo faktyczna jego precyzyjność, ale dla mnie wystarczająco tłumaczy zachowanie prywatnych składowych klas i ich widoczności w składowych klasy, nawet jeśli dotykają one egzemplarzy, ale tylko typu takiego samego, do jakiego one same należą.

Można zadać pytanie, a jak będzie w przypadku interfejsów? ;-) Uwaga: kawał!

Druga ciekawostka związana jest z wszechobecnymi adnotacjami, a szczególnie ich wykorzystaniu w tworzeniu aplikacji opartych o Java EE. Przedstawia się ją, a szczególnie ostatnio mnie absorbującą specyfikację EJB3 (wraz z JPA 1.0), jako bezinwazyjną, tzn. wymagania specyfikacji nie wprowadzają do klas żadnych zmian, które uniemożliwiłyby ich użycie poza środowiskiem serwera aplikacyjnego Java EE - zero wymaganych interfejsów, włączania do zadanej hierarchi dziedziczenia, itp.

Pytanie jakie mi zadano podczas ostatniej prezentacji specyfikacji EJB 3.0 dotyczyło obecności adnotacji w klasie, będącej z punktu widzenia serwera aplikacyjnego komponentem EJB (użycie adnotacji @Stateless) i jej uruchomieniu poza serwerem aplikacyjnym (co zdejmuje jej "specjalność").

Adnotacje wymagają importu klas je realizujących, np. @EJB będzie wymagało importu klasy javax.ejb.EJB. Pytanie jakie się pojawia to obecność klasy podczas kompilacji i uruchomienia (można postawić ogólniejszy problem, nie dotykający adotacji, a poruszający jedynie same deklaracje import). W którym momencie, klasa w imporcie będzie wymagana? Podczas kompilacji i uruchamiana, czy wyłącznie podczas kompilacji, a może jednak wyłącznie podczas uruchamiania? Dla zobrazowania problemu przedstawiam klasę, która wykorzystuje adnotację @EJB.
  package annotation;

import javax.ejb.EJB;

public class MissingAnnotationAtRuntime {

@EJB
private Object ejbRef;

public static void main(String[] args) {
System.out.println("Prezentacja programowania z adnotacjami bez nich podczas uruchomienia.");
}
}
Pytanie 1: Czy klasa skompiluje się bez dostępności klasy EJB?

Pytanie 2: Czy klasa może zostać uruchomiona bez dostępności klasy EJB?

Odpowiedź dostarczają poniższe sesje.
  $ java -version
java version "1.5.0_08"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_08-b03)
Java HotSpot(TM) Client VM (build 1.5.0_08-b03, mixed mode)

$ echo $CLASSPATH


$ javac annotation/MissingAnnotationAtRuntime.java
annotation/MissingAnnotationAtRuntime.java:3: package javax.ejb does not exist
import javax.ejb.EJB;
^
annotation/MissingAnnotationAtRuntime.java:7: cannot find symbol
symbol : class EJB
location: class annotation.MissingAnnotationAtRuntime
@EJB
^
2 errors
oraz
  $ echo $CLASSPATH


$ java -cp bin annotation.MissingAnnotationAtRuntime
Prezentacja programowania z adnotacjami bez nich podczas uruchomienia.
Zaskakujące? Dla mnie już nie!

09 lutego 2007

EJB 3.0 korzystając z JBoss AS 4.0.5.GA, JEMS Installer 1.2.0.GA i NetBeans IDE 5.5.1

1 komentarzy
Otwierając dzisiaj skrzynkę pocztową zobaczyłem wiadomość informującą o komentarzu do wpisu JBoss AS 4.0.5 z EJB 3.0 RC9 Patch 1 i NetBeans IDE 5.5.1, gdzie jonsnow napisał o usprawnieniu zestawiania środowiska z wykorzystaniem JEMS Installer.

Nie ukrywam, że zazwyczaj jestem sceptyczny do korzystania z narzędzi automatyzujących instalację, które nie tylko ukrywają faktycznie wykonywane kroki i na dodatek zapisują coś w rejestrach (pracuję na MS Windows), ale jeszcze przeczytałem informację o pewnej konkretnej konfiguracji JBoss AS, rozszerzenia EJB 3.0 i JEMS Installera, która miała prawo działać, co zniechęciło mnie zupełnie. Wiedząc, że JBoss AS 4.0.5 to nie jest serwer aplikacyjny Java EE 5 i daleko mu do niego (poza EJB3 oraz JPA, które serwera i tak nie wymaga) nie zagłębiałem się w ten temat dalej. Podobną sytuację mam w przypadku skorzystania z IBM WebSphere Application Server Version 6.1 Feature Pack for EJB 3, więc po co miałbym zajmować się JBoss AS 4? Poza tym pełniejszą implementację Java EE 5 mam chociażby w GlassFish v2 oraz Apache Geronimo v2. Mimo to, kiedy przeczytałem wiadomość od jonsnow pomyślałem, że mogę to sprawdzić. W końcu nic nie traciłem.

Co się okazało? Po około 30 minutach miałem zestawione kompletne środowisko do tworzenia semi-Java EE 5 aplikacji (semi wynika z niepełności JBoss AS 4.0.5 w realizowaniu Java EE 5 po stronie kontenera servletów - brak chociażby wstrzeliwania zależności, itp.).

Warto to odnotować, bo nie mogłem uwierzyć, że moje przestarzałe stereotypy o oprogramowaniu typu JEMS Installer wzięły kolejny raz górę nad zdrowym rozsądkiem. Wystarczyło poświęcić 15 minut na zapoznanie się z dokumentacją! Tylko, kto chciałby czytać dokumentację?!

Zacząłem od pobrania JEMS Installer 1.2.0.GA (właśnie owe GA było dla mnie kolejnym argumentem za sprawdzeniem jego możliwości). Uruchomienie instalacji było wskazane przez jonsnow, więc kopiuj-wklej (z drobnymi poprawkami dotyczącymi ścieżki instalacji JBoss AS 4) i ENTER.
  C:\>cd apps

C:\apps>java -jar jems-installer-1.2.0.GA.jar -installGroup ejb3 installpath=c:/apps/jboss-4.0.5.GA_ejb3
Obarczony stereotypami, uruchomiłem to w tle, a sam zabrałem się za bardziej wartościowe rzeczy niż przyglądanie się instalacji. Jakież było moje zdziwienie, kiedy dysk przestał intensywnie pracować po około 10-15 minutach! Pomyślałem, że zapewne Installer nie znalazł czegoś i zakończył swoje działanie z błędem myśląc, że zechciałbym mu pomóc. Zajrzałem na konsolę i co widzę:
C:\apps>java -jar jems-installer-1.2.0.GA.jar -installGroup ejb3 installpath=c:/apps/jboss-4.0.5.GA_ejb3
Looking for auto install resource...
/res/jbossauto-install.xml URL: jar:file:/C:/apps/jems-installer-1.2.0.GA.jar!/res/jbossauto-install.xml
PackageListener, install.log=C:\DOCUME~1\JLASKO~1\LOCALS~1\Temp\jems-install.log
[ Starting automated installation ]
creating Logfile: 'IzPack_Logfile_at_1171016018453.txt' in: 'C:\DOCUME~1\JLASKO~1\LOCALS~1\Temp\'
[ Starting to unpack ]
[ Processing package: ejb3-deployer (1/58) ]
[ Processing package: invokers-service (2/58) ]
[ Processing package: jta-service (3/58) ]
[ Processing package: mail-service (4/58) ]
[ Processing package: jboss-cache (5/58) ]
[ Processing package: jbossws5 (6/58) ]
[ Processing package: snmp-adaptor.sar (7/58) ]
[ Processing package: properties-service (8/58) ]
[ Processing package: javamail (9/58) ]
[ Processing package: jmx-console (10/58) ]
[ Processing package: jms-http-invoker (11/58) ]
[ Processing package: usertx-service (12/58) ]
[ Processing package: jboss-local-jdbc.rar (13/58) ]
[ Processing package: ear-deployer (14/58) ]
[ Processing package: security (15/58) ]
[ Processing package: naming (16/58) ]
[ Processing package: hibernate (17/58) ]
[ Processing package: jca-service (18/58) ]
[ Processing package: jboss-aop-jdk50-deployer (19/58) ]
[ Processing package: ejb-timer-service (20/58) ]
[ Processing package: iiop-service.xml (21/58) ]
[ Processing package: jms-socket-invoker (22/58) ]
[ Processing package: scheduler-service (23/58) ]
[ Processing package: web-console-jdk50 (24/58) ]
[ Processing package: jsr77-service (25/58) ]
[ Processing package: jgroups (26/58) ]
[ Processing package: quartz-rar (27/58) ]
[ Processing package: cache-invalidation-service.xml (28/58) ]
[ Processing package: jmx-core (29/58) ]
[ Processing package: bsh-deployer (30/58) ]
[ Processing package: jboss-ha-local-jdbc (31/58) ]
[ Processing package: mail-inflow-adaptor (32/58) ]
[ Processing package: jboss-ds (33/58) ]
[ Processing package: xmlresolver (34/58) ]
[ Processing package: jms-injvm-invoker (35/58) ]
[ Processing package: jsr88-service (36/58) ]
[ Processing package: jboss-xa-jdbc (37/58) ]
[ Processing package: jboss-ha-xa-jdbc (38/58) ]
[ Processing package: docs (39/58) ]
[ Processing package: binding-service (40/58) ]
[ Processing package: minimal (41/58) ]
[ Processing package: dynclassloader-service (42/58) ]
[ Processing package: ejb-deployer (43/58) ]
[ Processing package: uuid-key-generator (44/58) ]
[ Processing package: war-deployer (45/58) ]
[ Processing package: client-deployer-service (46/58) ]
[ Processing package: attribute-persistence-service (47/58) ]
[ Processing package: xmlservice (48/58) ]
[ Processing package: jmx-invoker-service (49/58) ]
[ Processing package: jboss-bean-deployer (50/58) ]
[ Processing package: client (51/58) ]
[ Processing package: jbossretro (52/58) ]
[ Processing package: rmi-http-invoker (53/58) ]
[ Processing package: jms-asf-rar (54/58) ]
[ Processing package: jbossmq-service (55/58) ]
[ Processing package: jdbc-metadata-service (56/58) ]
[ Processing package: default-jms-destintations (57/58) ]
[ Processing package: jboss-remoting (58/58) ]

calltarget:

jbossws-post-install:
[echo] Unpacking wstools to bin...
[unzip] Expanding: C:\apps\jboss-4.0.5.GA_ejb3\client\jbossws-client.jar into C:\apps\jboss-4.0.5.GA_ejb3\bin
[echo] Removing jbossws14 client jars...
[delete] Deleting 4 files from C:\apps\jboss-4.0.5.GA_ejb3\client
[delete] Deleting C:\apps\jboss-4.0.5.GA_ejb3\client\jboss-backport-concurrent.jar
[delete] Deleting C:\apps\jboss-4.0.5.GA_ejb3\client\jbossretro-rt.jar
[delete] Deleting C:\apps\jboss-4.0.5.GA_ejb3\client\jbossws14-client.jar
[delete] Deleting C:\apps\jboss-4.0.5.GA_ejb3\client\namespace.jar
[echo] jbossws done

calltarget:

chmod-bin:
[echo] Updating script executable modes
[echo] bin dir is c:/apps/jboss-4.0.5.GA_ejb3/bin
[ Unpacking finished. ]
creating Logfile: 'IzPack_Logfile_at_1171016018453.txt' in: 'C:\DOCUME~1\JLASKO~1\LOCALS~1\Temp\'
[ Writing the uninstaller data ... ]
[ Automated installation done ]

C:\apps>
Widzę migający kursor na konsoli i zakończoną poprawnie instalację (!) Zaczynało mi się podobać. Jako pierwszy moduł instalowany zobaczyłem ejb3-deployer, więc dawało mi to pewność, że jest lepiej niż początkowo myślałem.

Co mogłem dalej zrobić, aby sprawdzić poprawność instalacji? Uruchomić serwer.
jlaskowski@dev /cygdrive/c/apps/jboss-4.0.5.GA_ejb3
$ ./bin/run.sh
=========================================================================

JBoss Bootstrap Environment

JBOSS_HOME: c:\apps\jboss-4.0.5.GA_ejb3

JAVA: /cygdrive/c/apps/java5/bin/java

JAVA_OPTS: -Dprogram.name=run.sh -server -Xms128m -Xmx512m -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000

CLASSPATH: c:\apps\jboss-4.0.5.GA_ejb3\bin\run.jar;c:\apps\java5\lib\tools.jar

=========================================================================

11:38:51,437 INFO [Server] Starting JBoss (MX MicroKernel)...
11:38:51,437 INFO [Server] Release ID: JBoss [Zion] 4.0.5.GA (build: CVSTag=Branch_4_0 date=200610162339)
11:38:51,437 INFO [Server] Home Dir: C:\apps\jboss-4.0.5.GA_ejb3
11:38:51,437 INFO [Server] Home URL: file:/C:/apps/jboss-4.0.5.GA_ejb3/
11:38:51,437 INFO [Server] Patch URL: null
11:38:51,437 INFO [Server] Server Name: default
11:38:51,437 INFO [Server] Server Home Dir: C:\apps\jboss-4.0.5.GA_ejb3\server\default
11:38:51,437 INFO [Server] Server Home URL: file:/C:/apps/jboss-4.0.5.GA_ejb3/server/default/
11:38:51,437 INFO [Server] Server Log Dir: C:\apps\jboss-4.0.5.GA_ejb3\server\default\log
11:38:51,437 INFO [Server] Server Temp Dir: C:\apps\jboss-4.0.5.GA_ejb3\server\default\tmp
11:38:51,437 INFO [Server] Root Deployment Filename: jboss-service.xml
11:38:51,921 INFO [ServerInfo] Java version: 1.5.0_08,Sun Microsystems Inc.
11:38:51,921 INFO [ServerInfo] Java VM: Java HotSpot(TM) Server VM 1.5.0_08-b03,Sun Microsystems Inc.
11:38:51,921 INFO [ServerInfo] OS-System: Windows XP 5.1,x86
11:38:52,500 INFO [Server] Core system initialized
11:38:55,156 INFO [WebService] Using RMI server codebase: http://dev:8083/
11:38:55,187 INFO [Log4jService$URLWatchTimerTask] Configuring from URL: resource:log4j.xml
11:38:58,406 INFO [SocketServerInvoker] Invoker started for locator: InvokerLocator [socket://9.164.187.15:3873/]
11:39:04,031 INFO [ServiceEndpointManager] WebServices: jbossws-1.0.3.SP1 (date=200609291417)
11:39:08,671 INFO [SnmpAgentService] SNMP agent going active
11:39:09,500 INFO [CorbaNamingService] Naming: [IOR:000000000000002B49444C3A6F6D672E6F72672F436F734E616D696E672F4E616D696E67436F6E746578744578743A312E300000000000020000000000000
8000102000000000D392E3136342E3138372E313500000DC8000000114A426F73732F4E616D696E672F726F6F74000000000000050000000000000008000000004A414300000000010000001C0000000000010001000000010
10001000101090000000105010001000000210000006000000000000000010000000000000024000000200000007E00000000000000010000000D392E3136342E3138372E313500000DC900400000000000000000001004010
8060667810201010100000000000000000000000000000000000000000000002000000004000000000000001F0000000400000003000000010000002000000000000000020000002000000004000000000000001F000000040
00003]
11:39:09,875 INFO [CorbaTransactionService] TransactionFactory: [IOR:000000000000003049444C3A6F72672F6A626F73732F746D2F69696F702F5472616E73616374696F6E466163746F72794578743A312E
000000000200000000000000E8000102000000000D392E3136342E3138372E313500000DC8000000144A426F73732F5472616E73616374696F6E732F46000000050000000000000008000000004A414300000000010000001C
000000000100010000000105010001000101090000000105010001000000210000006000000000000000010000000000000024000000200000007E00000000000000010000000D392E3136342E3138372E313500000DC90040
00000000000000001004010008060667810201010100000000000000000000000000000000000000000000002000000004000000000000001F0000000400000003000000010000002000000000000000020000002000000004
0000000000001F0000000400000003]
11:39:10,593 INFO [Embedded] Catalina naming disabled
11:39:10,687 INFO [ClusterRuleSetFactory] Unable to find a cluster rule set in the classpath. Will load the default rule set.
11:39:10,687 INFO [ClusterRuleSetFactory] Unable to find a cluster rule set in the classpath. Will load the default rule set.
11:39:11,078 INFO [Http11BaseProtocol] Initializing Coyote HTTP/1.1 on http-0.0.0.0-8080
11:39:11,078 INFO [Catalina] Initialization processed in 391 ms
11:39:11,078 INFO [StandardService] Starting service jboss.web
11:39:11,093 INFO [StandardEngine] Starting Servlet Engine: Apache Tomcat/5.5.20
11:39:11,125 INFO [StandardHost] XML validation disabled
11:39:11,140 INFO [Catalina] Server startup in 62 ms
11:39:11,265 INFO [TomcatDeployer] deploy, ctxPath=/invoker, warUrl=.../deploy/http-invoker.sar/invoker.war/
11:39:11,500 INFO [WebappLoader] Dual registration of jndi stream handler: factory already defined
11:39:12,484 INFO [TomcatDeployer] deploy, ctxPath=/, warUrl=.../deploy/jbossweb-tomcat55.sar/ROOT.war/
11:39:12,687 INFO [TomcatDeployer] deploy, ctxPath=/jbossws, warUrl=.../tmp/deploy/tmp255jbossws-context-exp.war/
11:39:13,046 INFO [TomcatDeployer] deploy, ctxPath=/jbossmq-httpil, warUrl=.../deploy/jms/jbossmq-httpil.sar/jbossmq-httpil.war/
11:39:14,656 INFO [TomcatDeployer] deploy, ctxPath=/web-console, warUrl=.../deploy/management/console-mgr.sar/web-console.war/
11:39:15,562 INFO [MailService] Mail Service bound to java:/Mail
11:39:15,750 INFO [RARDeployment] Required license terms exist, view META-INF/ra.xml in .../deploy/jboss-ha-local-jdbc.rar
11:39:15,812 INFO [RARDeployment] Required license terms exist, view META-INF/ra.xml in .../deploy/jboss-ha-xa-jdbc.rar
11:39:15,843 INFO [RARDeployment] Required license terms exist, view META-INF/ra.xml in .../deploy/jboss-local-jdbc.rar
11:39:15,890 INFO [RARDeployment] Required license terms exist, view META-INF/ra.xml in .../deploy/jboss-xa-jdbc.rar
11:39:16,000 INFO [RARDeployment] Required license terms exist, view META-INF/ra.xml in .../deploy/jms/jms-ra.rar
11:39:16,031 INFO [RARDeployment] Required license terms exist, view META-INF/ra.xml in .../deploy/mail-ra.rar
11:39:16,078 INFO [RARDeployment] Required license terms exist, view META-INF/ra.xml in .../deploy/quartz-ra.rar
11:39:16,093 INFO [QuartzResourceAdapter] start quartz!!!
11:39:16,156 INFO [SimpleThreadPool] Job execution threads will use class loader of thread: main
11:39:16,203 INFO [QuartzScheduler] Quartz Scheduler v.1.5.2 created.
11:39:16,218 INFO [RAMJobStore] RAMJobStore initialized.
11:39:16,218 INFO [StdSchedulerFactory] Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
11:39:16,218 INFO [StdSchedulerFactory] Quartz scheduler version: 1.5.2
11:39:16,218 INFO [QuartzScheduler] Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
11:39:16,937 INFO [WrapperDataSourceService] Bound ConnectionManager 'jboss.jca:service=DataSourceBinding,name=DefaultDS' to JNDI name 'java:DefaultDS'
11:39:17,281 INFO [A] Bound to JNDI name: queue/A
11:39:17,281 INFO [B] Bound to JNDI name: queue/B
11:39:17,281 INFO [C] Bound to JNDI name: queue/C
11:39:17,281 INFO [D] Bound to JNDI name: queue/D
11:39:17,281 INFO [ex] Bound to JNDI name: queue/ex
11:39:17,359 INFO [testTopic] Bound to JNDI name: topic/testTopic
11:39:17,359 INFO [securedTopic] Bound to JNDI name: topic/securedTopic
11:39:17,359 INFO [testDurableTopic] Bound to JNDI name: topic/testDurableTopic
11:39:17,390 INFO [testQueue] Bound to JNDI name: queue/testQueue
11:39:17,531 INFO [UILServerILService] JBossMQ UIL service available at : /0.0.0.0:8093
11:39:17,593 INFO [DLQ] Bound to JNDI name: queue/DLQ
11:39:17,718 INFO [ConnectionFactoryBindingService] Bound ConnectionManager 'jboss.jca:service=ConnectionFactoryBinding,name=JmsXA' to JNDI name 'java:JmsXA'
11:39:17,750 INFO [TomcatDeployer] deploy, ctxPath=/jmx-console, warUrl=.../deploy/jmx-console.war/
11:39:18,078 INFO [Http11BaseProtocol] Starting Coyote HTTP/1.1 on http-0.0.0.0-8080
11:39:18,218 INFO [ChannelSocket] JK: ajp13 listening on /0.0.0.0:8009
11:39:18,328 INFO [JkMain] Jk running ID=0 time=0/125 config=null
11:39:18,406 INFO [Server] JBoss (MX MicroKernel) [4.0.5.GA (build: CVSTag=Branch_4_0 date=200610162339)] Started in 26s:969ms
To również zadziałało! No, dobrze pomyślałem, czas sprawdzić uruchomienie przykładowej aplikacji Java EE 5.

W NetBeans IDE 5.5.1 Daily Build (z dnia 31 stycznia 2007) zdefiniowałem nową instancję serwera (zakładka Runtime). JBoss AS działał, więc NetBeans IDE od razu rozpoznał sytuację i pozwolił mi na przejrzenie zainstalowanych aplikacji. Nic nie było. Zabrałem się za stworzenie aplikacji przemysłowej (ang. Enterprise Application) - File->New Project...->Enterprise->Enterprise Application. Zdefiniowałem nazwę dla projektu simple (wierząc, że z projektami typu simple JBoss AS sobie poradzi ;-)) i wybrałem serwer JBoss AS, który właśnie zdefiniowałem. Pozostałe rzeczy pozostawiłem bez zmian. Na węźle simple-ejb wybrałem z menu kontekstowego New->Session Bean....
  package pl.jaceklaskowski.simple.beans;

import javax.ejb.Stateless;

@Stateless
public class NewSessionBean implements NewSessionLocal {

public String powitaj(String kogo) {
return "Witam " + kogo + "!";
}

}
Komponent sesyjny EJB3 już miałem, więc pora do warstwy prezentacji, czyli utworzenie servletu. Dlaczego servletu? Po pierwsze, ponieważ podlega wstrzeliwaniu zależności w Java EE 5, a po drugie jest do tego wizard w NetBeans IDE. Wybrałem menu New->Servlet... z menu kontekstowego na węźle simple-war.

Przypisałem servlet do adresu /powitaj i początkowo mój servlet wyglądał tak:
  package pl.jaceklaskowski.simple.servlet;

import java.io.*;
import javax.ejb.EJB;

import javax.servlet.*;
import javax.servlet.http.*;
import pl.jaceklaskowski.simple.beans.NewSessionLocal;

public class WywolajNewSessionBean extends HttpServlet {

@EJB
private NewSessionLocal newSessionLocal;

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet WywolajNewSessionBean</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>" + newSessionLocal.powitaj(request.getParameter("kogo")) + "</h1>");
out.println("</body>");
out.println("</html>");
out.close();
}

// usunięte, nieistotne dla wpisu, metody servletu
}
Po stworzeniu servletu, wcisnąłem klawisz F6 uruchamiający projekt główny, co w moim przypadku było właśnie projektem simple. Przełączyłem się na konsolę JBoss AS, gdzie pojawiły się wpisy zdarzeń:
12:07:24,750 INFO  [EARDeployer] Init J2EE application: file:/C:/apps/jboss-4.0.5.GA_ejb3/server/default/deploy/simple.ear
12:07:25,187 INFO [Ejb3Deployment] EJB3 deployment time took: 297
12:07:25,234 INFO [JmxKernelAbstraction] installing MBean: jboss.j2ee:ear=simple.ear,jar=simple-ejb.jar,name=NewSessionBean,service=EJB3 with dependencies:
12:07:25,500 INFO [EJBContainer] STARTED EJB: pl.jaceklaskowski.simple.beans.NewSessionBean ejbName: NewSessionBean
12:07:25,562 INFO [EJB3Deployer] Deployed: file:/C:/apps/jboss-4.0.5.GA_ejb3/server/default/tmp/deploy/tmp288simple.ear-contents/simple-ejb.jar
12:07:25,562 INFO [TomcatDeployer] deploy, ctxPath=/simple-war, warUrl=.../tmp/deploy/tmp288simple.ear-contents/simple-war-exp.war/
12:07:25,750 INFO [EARDeployer] Started J2EE application: file:/C:/apps/jboss-4.0.5.GA_ejb3/server/default/deploy/simple.ear
czyli wszystko w porządku. NetBeans uruchomił mi przeglądarkę z aplikacją, więc nadal było w porządku. Po wpisaniu adresu http://localhost:8080/simple-war/powitaj?kogo=Jacek okazało się jednak, że mój entuzjazm do bezawaryjnego zestawienia środowiska zmalał - pojawił się wyjątek! I to jaki - NullPointerException!

Analiza kodu wskazała na...brak wstrzeliwania zależności (DI) w JBoss AS. Przez moment nie mogłem w to uwierzyć. Jak to? Przecież Java EE 5 pozwala, aby servlet korzystał z DI. Może źle wyszukuje w JNDI? Trwało to jakąś chwilę, aż przypomniałem sobie o braku wsparcia dla DI w Tomcat 5, który był kontenerem servletów w JBoss AS.

Na pomoc pośpieszył mi NetBeans IDE z menu kontekstowym w otwartym edytorze z klasą servletu - Enterprise Resources->Call Enterprise Bean. W ten sposób NetBeans IDE dodał mi metodę lookupNewSessionBean, którą wykorzystałem w metodzie servletu:
  package pl.jaceklaskowski.simple.servlet;

import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.servlet.*;
import javax.servlet.http.*;
import pl.jaceklaskowski.simple.beans.NewSessionLocal;

public class WywolajNewSessionBean extends HttpServlet {

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet WywolajNewSessionBean</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>" + lookupNewSessionBean().powitaj(request.getParameter("kogo")) + "</h1>");
out.println("</body>");
out.println("</html>");
out.close();
}

private NewSessionLocal lookupNewSessionBean() {
try {
Context c = new InitialContext();
return (NewSessionLocal) c.lookup("java:comp/env/ejb/NewSessionBean");
} catch(NamingException ne) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE,"exception caught" ,ne);
throw new RuntimeException(ne);
}
}

// usunięte, nieistotne dla wpisu, metody servletu

}
Ponownie wcisnąłem F6, wpisałem adres http://localhost:8080/simple-war/powitaj?kogo=Jacek i...kolejny raz wyjątek!

Tym razem już wiedziałem, że należy zadeklarować zależność w deskryptorze instalacji aplikacji internetowej (WEB-INF/web.xml), ale nie wiedziałem jaka musi być postać. Przypomniałem sobie o artykule na stronach NetBeans IDE o integracji z JBoss AS 4, który planowałem przeczytać - EJB 3.0 Enterprise Beans for the JBoss Application Server. Teraz była najwłaściwsza pora. Odszukałem sekcji o połączeniu servleta z komponentem EJB i poprawiłem metodę lookupNewSessionBean, aby tym razem wyszukiwała komponentu pod adresem simple/NewSessionBean/local. Ostatecznie kod servletu wyglądał tak:
  package pl.jaceklaskowski.simple.servlet;

import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.servlet.*;
import javax.servlet.http.*;
import pl.jaceklaskowski.simple.beans.NewSessionLocal;

public class WywolajNewSessionBean extends HttpServlet {

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet WywolajNewSessionBean</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>" + lookupNewSessionBean().powitaj(request.getParameter("kogo")) + "</h1>");
out.println("</body>");
out.println("</html>");
out.close();
}

private NewSessionLocal lookupNewSessionBean() {
try {
Context c = new InitialContext();
return (NewSessionLocal) c.lookup("simple/NewSessionBean/local");
} catch(NamingException ne) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE,"exception caught" ,ne);
throw new RuntimeException(ne);
}
}

// usunięte, nieistotne dla wpisu, metody servletu
}
Ponownie F6, wpisanie adresu http://localhost:8080/simple-war/powitaj?kogo=Jacek i widzę pomyślnie uruchomioną aplikację!

Warto było! Dzięki jonsnow za komentarz!

08 lutego 2007

Java Persistence - 3.5 Encyjni obserwatorzy i metody przechwytujące

5 komentarzy
To nie było proste - znaleźć polski odpowiednik dla angielskiego listener. Słuchacz? Nasłuchiwacz? Nie bardzo. Ale obserwator? Dlaczego nie! Szczególnie, że mimo, że słuchacz (bliższy listener'owi) jedynie nasłuchuje, a obserwator może kojarzyć się jedynie z obserwowaniem lub oglądaniem, to i tak niesie dokładnie tą samą informację - reagowania na zdarzenia, a właśnie o to mi chodzi w tłumaczeniu. A może powinienem użyć słowa monitor encyjny? Dzisiaj postanowiłem zrelacjonować temat z użyciem słowa obserwator.

Dowolna metoda może przechwytywać zdarzenia związane ze stadiami rozwojowymi encji - metoda przechwytująca zdarzenia rozwojowe, w skrócie metoda przechwytująca (ang. lifecycle callback method).

Metoda przechwytująca może być zdefiniowana w klasie encji, mapowanej klasie bazowej (dla przypomnienia z poprzedniego wpisu z relacją lektury sekcji 2.1.9 o dziedziczeniu: mapowana klasa bazowa - klasa bazowa, która nie jest encją, choć dostarcza trwałego stanu i informacji o mapowaniu - jest bazą dla rozwoju innych bardziej zaawansowanych encji nie dostarczając kompletnej realizacji modelowanego bytu), lub klasa obserwatora (ang. entity listener class) związana z encją albo mapowaną klasą bazową. Klasa obserwatora encyjnego jest klasą, której metody są wywołane w odpowiedzi na zdarzenia rozwojowe encji (ang. entity lifecycle events). Dowolna liczba klas obserwatorów może być przypisana do encji bądź mapowanej klasy bazowej.

Domyślni obserwatorzy encji - obserwatorzy przypisani do wszystkich encji w jednostce utrwalania (ang. persistence unit) - są definiowani w deskryptorze XML.

Metody przechwytujące zdarzenia i klasy obserwatorów encyjnych są zdefiniowane za pomocą adnotacji lub deskryptora XML. Do przypisania klas obserwatorów encyjnych do klasy encji bądź mapowanej klasy bazowej służy adnotacja @EntityListeners. Kolejność wyzwalania obserwatorów wyznaczana jest przez kolejność ich deklaracji w adnotacji @EntityListeners. Deskryptor XML może służyć jako alternatywne miejsce do określania kolejności wyzwalania obserwatorów za pomocą adnotacji lub do zmiany kolejności wyznaczonej za pomocą adnotacji @EntityListeners.

Dowolny podzbiór lub kombinacja adnotacji może być przypisana do klasy encji, mapowanej klasy bazowej, czy klasy obserwatora. Pojedyńcza klasa nie może mieć więcej niż jednej metody przechwytującej dla stadium rozwojowego. Ta sama metoda może być zdefiniowana do przechwytywania wielu zdarzeń rozwojowych.

Dowolna ilość klas encji i mapowanych klas bazowych w hierarchi dziedziczenia może definiować klasy obserwatorów i/lub metody przechwytujące zdarzenia rozwojowe bezpośrednio (na klasie).

Klasa obserwatora encyjnego musi posiadać publiczny i bezparametrowy konstruktor.

Obserwator jest bezstanowy. Nie istnieje pojęcie cykli życiowych obserwatora.

Reguły funkcjonowania obserwatorów:
  • Metody obserwatora mogą rzucać niekontrolowane wyjątki (ang. unchecked/runtime exceptions), których wystąpienie w trakcie aktywnej transakcji, powoduje jej wycofanie.
  • Metody obserwatora mogą korzystać z usług środowiska - JNDI, JDBC, JMS oraz komponentów przemysłowych (ang. enterprise beans)
  • Metody obserwatora w przenośnej aplikacji, w ogólności, nie powinny wywoływać operacji EntityManager oraz Query, korzystać z innych instancji encji (czy to oznacza, że encje nie są traktowane jako komponenty przemysłowe w JPA?!) lub modyfikować relacje
Obserwatorzy encyjni wywołani w środowisku serwera aplikacyjnego współdzielą kontekst przestrzeni nazw JNDI związanej encji, a metody obserwatora są wywołane w kontekście transakcyjnym i bezpieczeństwa wywołującego komponentu w czasie, kiedy metoda została wywołana, np. jeśli zatwierdzenie transakcji nastąpi jako poprawnego wykonania metody biznesowej komponentu sesyjnego oznaczonej atrybutem transakcyjnym RequiresNew, metody obserwatora PostPersist oraz PostRemove są wykonywane w kontekście nazw, transakcyjnym i bezpieczeństwa tego komponentu sesyjnego.

3.5.1 Metody przechwytujące

Metody przechwytujące mogą być zdefiniowane na klasie obserwatora encyjnego i/lub bezpośrednio na klasie encji bądź mapowanej klasy bazowej.

Metody przechwytujące są oznaczone adnotacjami wskazującymi przechwytywane zdarzenia lub są zdefiniowane w deskryptorze XML.

Adnotacje używane do oznaczania metod przechwytujących na klasie encji bądź mapowanej klasie bazowej i na metodach obserwatora są takie same. Sygnatury pojedyńczych metod różnią się jednak (dla przypomnienia: podobnie było z metodami przechwytującymi, wtedy zwanymi metodami zwrotnymi, dla komponentów przemysłowych). Metody przechwytujące zdefiniowane w klasie encji bądź mapowanej klasy bazowej mają następującą sygnaturę:

void <DOWOLNA_NAZWA_METODY>()

, a zdefiniowane poza hierarchią dziedziczenia encji, tj. w dedykowanej klasie obserwatora:

void <DOWOLNA_NAZWA_METODY>(Object)

Parametr wejściowy typu Object jest instancją encji, dla której metoda będzie wywołana. Typ może być zadeklarowany jako właściwy typowi encji.

Metody przechwytujące mogą być public, private, protected lub domyślne, ale nie mogą być static czy final (chyba po to, aby umożliwić ich przysłanianie przez podklasy generowane przez dostawcę JPA).

Następujące adnotacje są dostępne do określenia metod przechwytujących odpowiednich typów:
  • @PrePersist
  • @PostPersist
  • @PreRemove
  • @PostRemove
  • @PreUpdate
  • @PostUpdate
  • @PostLoad
3.5.2 Działanie encyjnych metod przechwytujących

Metody udekorowane @PrePersist i @PreRemove są wywoływane zanim nastąpi wywołanie metod persist i remove dla danej encji, odpowiednio. Instancja encji, dla których wywołano merge, która z kolei spowoduje utworzenie nowej instancji zarządzanej encji wyzwoli metodę udekorowaną @PrePersist dla tej encji, po tym jak stan encji przyłączanej zostanie przekopiowany. Metody @PrePersist i @PreRemove będą kaskadowo wyzwolone na wszystkich powiązanych relacyjnie encjach. Metody @PrePersist oraz @PreRemove zostaną uruchomione podczas wykonania persist, merge i remove.

Metody @PostPersist oraz @PostRemove są wywołane dla encji właśnie utrwalonej bądź usuniętej bezpośrednio w bazie danych (co może nastąpić w momencie wywołania metod persist czy remove, po wywołaniu flush lub ostatecznie podczas zatwierdzania transakcji). Metody przechwytujące będą wywołane kaskadowo dla wszystkich powiązanych relacyjnie encji. Wartości generowanych kluczy głównych są dostępne w metodzie @PostPersist.

Metody @PreUpdate oraz @PostUpdate są wywołane przed i po wykonaniu operacji update bezpośrednio w bazie danych (co może nastąpić w momencie modyfikacji stanu instancji encji, po wywołaniu flush lub ostatecznie podczas zatwierdzania transakcji).
Specyfikacja nie gwarantuje wykonania @PreUpdate i @PostUpdate, kiedy encja jest utrwalona, a następnie zmodyfikowana w jednej transakcji lub kiedy encja jest zmodyfikowana, a następnie usunięta w trakcie jednej transakcji.

Metoda @PostLoad jest wywołana po załadowaniu stanu encji z bazy danych do aktualnego kontekstu utrwalania lub po wykonaniu refresh. Metoda @PostLoad jest wywołana zanim wynik zapytania jest zwrócony lub wykorzystany lub zanim nastąpi nawigacja (przejście) po relacji.

Specyfikacja nie gwarantuje kolejności wykonania metod przechwytujących na instancji encji przed lub po kaskadowym wykonaniu metod na jej relacyjnie powiązanych encjach.

Sekcja 3.5.3 przedstawia ciekawy przykład zaawansowanej encji Account wraz z obserwatorem AlertMonitor. Pierwszy raz spotykam się z tak zaawansowaną klasą encji. Zazwyczaj były to klasy z atrybutami (POJO) i adnotacjami mapującymi. Tym razem specyfikacja prezentuje klasę z obserwatorem z metodą @PostPersist, która przesyła informację o właśnie założonym koncie do systemu monitorującego oraz metodami przechwytującymi @PrePersist (sprawdzenie kwoty wpłaty przed otwarciem konta) i @PostLoad (wyliczającą nietrwały atrybut wskazujący na typ konta) w samej klasie encji.

3.5.4 Wiele metod przechwytujących dla pojedyńczego zdarzenia rozwojowego

Jeśli zdefiniowano wiele metod przechwytujących dla pojedyńczego zdarzenia rozwojowego, kolejność wykonania metod jest następująca:
  • Domyślni obserwatorzy, jeśli istnieją, są wykonani najpierw, w porządku wyznaczonym przez deskryptor XML. Domyślni obserwatorzy związane są ze wszystkimi encjami w jednostce utrwalania chyba, że wyłączono je przez adnotację @ExcludeDefaultListeners lub element exclude-default-listeners w deskryptrze XML.
  • Metody przechwytujące obserwatora zdefinowane dla klasy encji bądź mapowanej klasy bazowej są wywołane w kolejności definicji ich klas w adnotacji @EntityListeners.
  • Jeśli kilka klas encji bądź mapowanych klas bazowych w hierarchii dziedziczenia encji definiuje obserwatorów, obserwatorzy zdefinowani w klasach bazowych są wywołani zanim wywołani zostaną obserwatorzy klas potomnych w kolejności dziedziczenia, aż do samej klasy encji. Adnotacja @ExcludeSuperclassListeners lub element exclude-superclass-listeners może być wykorzystana w klasie encji bądź mapowanej klasy bazowej do wyłączenia wykonania obserwatorów klas bazowych dla encji bądź mapowanej klasy bazowej. Wyłączenie obserwatorów dotyczy klasy, w której znajduje się adnotacja @ExcludeSuperclassListeners (lub odpowiedni wpis w deskryptorze XML) i jej klas potomnych (wyłączeni obserwatorzy mogą być ponownie włączeni/uaktywnieni za pomocą ich deklaracji przez adnotację @EntityListeners bądź entity-listeners). Adnotacja @ExcludeSuperclassListeners (lub exclude-superclass-listeners) nie wyłącza domyślnych obserwatorów (tj. tych zdefiniowanych w deskryptorze XML).
  • Jeśli metoda przechwytująca dla pewnego zdarzenia rozwojowego jest zdefiniowana na klasie encji i/lub klasach bazowych (czy to klas bazowych encyjnych - będącymi również encjami, czy mapowanych klas bazowych), metody przechwytujące na klasie encji i/lub mapowanych klasach bazowych są wywołane po wywołaniu metod przechwytujących dla wcześniejszych klas bazowych (pod względem hierarchii dziedziczenia), tj. metody przechwytujące dla klas wyżej w hierarchii (bliżej typowi Object a nie klasie encji) są wywoływane wcześniej. Klasa ma prawo przesłonić odziedziczoną metodę przechwytującą o tym samym rodzaju przechwytywanego zdarzenia, co sprawi, że przesłonięta metoda nie będzie wywołana. Jednakże, jeśli metoda przesłania odziedziczoną metodę przechwytującą, ale definiuje inne zdarzenie do przechwycenia lub jeśli nie jest metodą przechwytującą, metoda przesłonięta będzie wywołana (ponieważ jest metodą przechwytującą zdarzenia innego rodzaju bądź w drugim przypadku w ogóle jest metodą przesłoniętą podczas, gdy przesłaniająca nie).
  • Tylko poprawne zakończenie wykonania metody przechwytującej spowoduje wykonanie kolejnych metod przechwytujących.
  • Deskryptor XML może służyć do zmiany kolejności wykonywania metod przechwytujących określonym przez adnotacje.
Sekcja 3.5.5 przedstawia przykład, który demonstruje kolejność wykonania metod przechwytujących encji i jej mapowanej klasie bazowej. Próba opisu przykładu spowoduje wyłącznie jego zaciemnienie. Warto zajrzeć do specyfikacji dla zapoznania się z nim.

3.5.6 Wyjątki

Metody przechwytujące mogą zakończyć się niekontrolowanymi wyjątkami. Niekontrolowany wyjątek powoduje wycofanie aktywnej transakcji, w której wykonywana jest metoda przechwytująca. Kolejne metody przechwytujące nie zostaną wywołane po wystąpieniu niekontrolowanego wyjątku.

3.5.7 Deklaracja obserwatorów i metod przechwytujących w deskryptorze XML

Deskryptor XML może być alternatywnym miejscem definiowania klas obserwujących i metod przechwytujących oraz kolejności ich wykonania (nadpisując konfigurację poprzez adnotacje).

Deklaracja metod przechwytujących

Element entity-listener w deskryptorze XML służy do deklaracji metod przechwytujących. Rodzaj zdarzenia definiowany jest przez elementy pre-persist, post-persist, pre-remove, post-remove, pre-update, post-update i post-load.

Co najwyżej jedna metoda obserwatora może być metodą przechwytującą dany rodzaj zdarzenia, bez względu, czy deklaracja następuje poprzez deskryptor XML, adnotacje, czy ich kombinację.

Deklaracja przypisania metod przechwytujących do encji

Podelement entity-listeners elementu persistence-unit-defaults definiuje domyślnych obserwatorów dla danej jednostki utrwalania.

Podelement entity-listeners elementów entity lub mapped-superclass definiuje obserwatorów dla encji lub mapowanej klasie bazowej, odpowiednio (wraz z ich klasami pochodnymi).

Przypisanie obserwatorów do klas encji jest addytywne (sumuje się). Obserwator przypisany do klasy bazowej encji lub mapowanej klasy bazowej jest również przypisany do nich.

Element exclude-superclass-listeners określa, że metody przechwytujące dla klas bazowych nie będą uruchomione dla klasy encji lub mapowanej klasy bazowej i podklas.

Element exclude-default-listeners określa, że domyślni obserwatorzy nie będą uruchomieni dla klasy encji lub mapowanej klasy bazowej i podklas.

Dosłowne wypisanie wyłączonych obserwatorów domyślnych lub klas bazowych dla danej encji lub mapowanej klasy bazowej powoduje, że wymienieni obserwatorzy będą wyłączeni dla tej encji lub mapowanej klasy bazowej i podklas.

Ostateczna kolejność wykonania metod przechwytujących dla pojedyńczego zdarzenia rozwojowego wyznaczana jest zgodnie z w/w regułami (opisanymi wyżej jako 3.5.4 Wiele metod przechwytujących dla pojedyńczego zdarzenia rozwojowego).

To była na prawdę interesująca sekcja! Przykłady były niebanalne i spotkałem się z tymi konstrukcjami po raz pierwszy. Możliwość przechwytywania zdarzeń była możliwa również w komponentach przemysłowych (wtedy nazywałem je biznesowymi). W poprzednich wersjach specyfikacji Java EE (1.4 i wcześniejszych) mechanizm przechwytywania był jedynie dostępny po wdrożeniu rozwiązań AOP (programowanie aspektowe, ang. aspect-oriented programming). Teraz nie trzeba się nimi zajmować (przynajmniej na początku), a jedynie postępować zgodnie z wytycznymi opisanymi w specyfikacji. Znaczne uproszczenie i większa przenośność aplikacji.

Kolejny rozdział to publiczny interfejs klasy Query - 3.6 Query API.