Tworzenie aplikacji EJB 3.0 z GlassFish v2, Apache Maven 2 i NetBeans IDE 6.0

Z Jacek Laskowski - Wiki Projektanta Java EE

Podczas zestawiania środowiska do odtworzenia potencjalnego błędu w działaniu Apache OpenEJB, gdzie metoda remove() może być błędnie postrzegana jako metoda niedozwolona (GERONIMO-3452 Stateless Session EJBs cannot contain a remove() method) postanowiłem wykorzystać dostępne narzędzia, aby spróbować zminimalizować potrzebny czas na jego stworzenie. Postanowiłem podejść do tematu napierw od strony serwera aplikacji Java EE 5 - GlassFish v2 - będącego implementacją referencyjną specyfikacji Java EE 5 i po poprawnym uruchomieniu, bądź jego braku, odpowiednio zareagować na zgłoszenie.

Spis treści

Oprogramowanie

Do zestawienia środowiska w celu stworzenia aplikacji EJB 3.0 wykorzystamy następujące projekty:

Zakłada się, że powyższe oprogramowanie jest zainstalowane i działa poprawnie. Instalacja oprogramowania sprowadza się do pobrania i rozpakowania paczek w wybranym katalogu.

Instalacja wtyczki NetBeans Maven2 project support polega na uruchomieniu NetBeans IDE 6.0 i wybraniu menu Tools > Plugins, po którym pojawi się okienko dialogowe, gdzie wybieramy zakładkę Available Plugins i wybieramy wtyczkę.

Kompletny projekt jest dostępny jako ejb3-remove-stateless.zip.

Utworzenie struktury projektowej

Utworzenie projektu macierzystego - ejb3-remove-stateless

Rozpoczynamy od utworzenia projektu macierzystego (głównego) z punktu widzenia organizacji projektu zarządzanego przez Apache Maven 2 (dalej zwanym m2). Będzie to projekt typu pom, który będzie zawierał dwa podprojekty - moduł ziarna EJB oraz klienta zdalnego.

Mamy do wyboru tworzenie projektu ręcznie - za pomocą polecenia mvn, bądź korzystając z wtyczki NetBeans Maven2 project support. Wykorzystanie wtyczki okazało się znacząco uprościć proces zestawiania środowiska i pracy z m2. Dzięki wtyczce można wykonać większość czynności zarządczych z poziomu IDE, które do tej pory były wykonywane z poziomu linii poleceń.

Zakładając poprawną instalację wtyczki NetBeans Maven2 project support wybieramy menu File > New Project, a dalej kategorię Maven i pozycję Maven Project.

Grafika:ejb3-remove-stateless-createnewmavenproject.PNG

Wciskamy przycisk Next >.

Grafika:ejb3-remove-stateless-mavenarchetype.png

Wciskamy ponownie przycisk Next > pozostając przy wyborze archetypu Maven Quickstart Project.

W kolejnym panelu podajemy następujące wartości projektu:

  • Project Name: ejb3-remove-stateless
  • Project Location: C:\
  • Group Id: pl.jaceklaskowski.javaee
  • Version: 1.0
  • Package: pl.jaceklaskowski.javaee


Grafika:ejb3-remove-stateless-projectnameandlocation.png

Wartości mogą się różnić od podanych, jednakże dalsza część artykułu zakłada te podane wyżej.

Kończymy tworzenie projektu przyciskiem Finish.

Ostatecznie utworzy się projekt typu jar, którego konfigurację zmienimy, aby pełnił rolę projektu macierzystego. Wybieramy menu Properties znajdujące się pod prawym przyciskiem myszy po zaznaczeniu nowopowstałego projektu ejb3-remove-stateless. Zmieniamy pole Packaging z jar na pom.

Grafika:ejb3-remove-stateless-packaging.png

Modyfikujemy również właściwości projektu pod względem wykorzystanej wersji Javy.

Grafika:ejb3-remove-stateless-sourcelevel.png

Zatwierdzamy zmiany wybierając przycisk OK.

Kończymy pracę przygotowania projektu kasując katalog src, który jest tworzony domyślnie podczas tworzenia projektu, a który nie będzie nam potrzebny. Do usunięcia katalogu konieczne jest przejście do zakładki Files, gdzie pojawi się katalog src z dostępnym menu Delete.

Grafika:ejb3-remove-stateless-srcdelete.png

Usuwamy z projektu bibliotekę testową junit-3.8.1 (później podniesiemy ją do wersji 4.2)

Grafika:ejb3-remove-stateless-removedependency.png

Utworzenie podprojektu - ejb3-remove-stateless-ejb3

Tworząc podprojekt ejb3-remove-stateless-ejb3 skorzystamy ponownie z możliwości NetBeans IDE 6.0. Postępujemy podobnie jak w przypadku tworzenia projektu macierzystego ejb3-remove-stateless zmieniając jedynie położenie docelowego podprojektu tak, aby wskazywał na katalog zawierający projekt macierzysty (jest to analogiczne do wykonania polecenia mvn create:archetype będąc w katalogu macierzystym).

Podajemy następujące wartości projektu:

  • Project Name: ejb3-remove-stateless-ejb3
  • Project Location: C:\ejb3-remove-stateless
  • Group Id: pl.jaceklaskowski.javaee
  • Version: 1.0
  • Package: pl.jaceklaskowski.javaee


Grafika:ejb3-remove-stateless-subproject-ejb3.png

Kończymy tworzenie projektu przyciskiem Finish.

Poprawne utworzenie projektu spowoduje modyfikację pliku pom.xml projektu macierzystego o wpis dotyczący modułu ejb3-remove-stateless-ejb3.

<modules>
  <module>ejb3-remove-stateless-ejb3</module>
</modules>

Podobnie jak miało to miejsce w przypadku projektu macierzystego, usuwamy bibliotekę testową junit-3.8.1. Dodatkowo usuwamy katalog przeznaczony na testy jednostkowe Test Packages (konieczne jest wybranie zakładki Files, gdzie rozwijamy węzeł ejb3-remove-stateless-ejb3 (jar) > src, gdzie usuwamy katalog test oznaczony czerwonym wykrzyknikiem).

Grafika:ejb3-remove-stateless-deletetestdir.png

Konfigurujemy projekt zmieniając Packaging na ejb. Wybieramy menu Properties znajdujące się pod prawym przyciskiem myszy i w okienku dialogowym zmieniamy pole Packaging z jar na ejb.

Grafika:ejb3-remove-stateless-packaging-ejb.png

Zatwierdzamy zmiany wybierając przycisk OK.

Usuwamy klasę pl.jaceklaskowski.javaee.App (z sekcji Source Packages). Na zakończenie konfigurujemy projekt jako zawierający ziarno EJB 3.0. Zmiana wymaga bezpośredniej edycji pliku pom.xml, do którego dodajemy konfigurację wtyczki maven-ejb-plugin.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-ejb-plugin</artifactId>
    <configuration>
        <ejbVersion>3.0</ejbVersion>
    </configuration>
</plugin>

Po modyfikacji plik pom.xml powinien prezentować się następująco:

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <parent>
        <artifactId>ejb3-remove-stateless</artifactId>
        <groupId>pl.jaceklaskowski.javaee</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <groupId>pl.jaceklaskowski.javaee</groupId>
    <artifactId>ejb3-remove-stateless-ejb3</artifactId>
    <name>ejb3-remove-stateless-ejb3</name>
    <packaging>ejb</packaging>
    <version>1.0</version>
    <url>http://www.JacekLaskowski.pl</url>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-ejb-plugin</artifactId>
                <configuration>
                    <ejbVersion>3.0</ejbVersion>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Utworzenie podprojektu - ejb3-remove-stateless-client

Podobnie jak w przypadku poprzedniego podprojektu tworzymy projekt ejb3-remove-stateless-client. Najistotniejsze, aby Project Location wskazywał na katalog projektu macierzystego ejb3-remove-stateless.

Podajemy następujące wartości projektu:

  • Project Name: ejb3-remove-stateless-client
  • Project Location: C:\ejb3-remove-stateless
  • Group Id: pl.jaceklaskowski.javaee
  • Version: 1.0
  • Package: pl.jaceklaskowski.javaee


Grafika:ejb3-remove-stateless-subproject-client.png

Kończymy przygotowanie projektu usuwając bibliotekę testową junit-3.8.1.jar (Test Libraries), klasę pl.jaceklaskowski.javaee.App (Source Packages) oraz pl.jaceklaskowski.javaee.AppTest (Test Packages).

Kompletna struktura projektowa

Struktura projektowa prezentuje się następująco:

Grafika:ejb3-remove-stateless-projects.png

Utworzenie ziarna EJB - Removable

Deklaracja zależności biblioteki EJB 3.0

Tworzenie ziarna EJB rozpoczynamy od zdefiniowania zależności projektowej.

Grafika:ejb3-remove-stateless-addlibrary.png

Dodajemy bibliotekę EJB 3.0 dostarczaną jako org.apache.geronimo.specs:geronimo-ejb_3.0_spec:1.0.

Grafika:ejb3-remove-stateless-addlibrary-geronimoejb3.png

Zdalny interfejs biznesowy - RemovableRemote

Tworzymy zdalny interfejs biznesowy RemovableRemote (klasa pl.jaceklaskowski.javaee.RemovableRemote) w projekcie ejb3-remove-stateless-ejb3.

package pl.jaceklaskowski.javaee;

import javax.ejb.Remote;

@Remote
public interface RemovableRemote {
    public void remove(String message);
}

Klasa ziarna - RemovableBean

Tworzymy klasę ziarna RemovableBean (klasa pl.jaceklaskowski.javaee.RemovableBean) realizujący interfejs biznesowy RemovableRemote.

package pl.jaceklaskowski.javaee;

import javax.ejb.Stateless;

@Stateless(mappedName="Removable")
public class RemovableBean implements RemovableRemote {
    public void remove(String msg) {
        System.out.println(msg);
    }
}

Wykorzystanie atrybutu mappedName adnotacji @Stateless pozwala na przypisanie nazwy, pod którą będzie można odszukać ziarno EJB przez zdalnego klienta (więcej w How are Global JNDI names assigned to Session / Entity beans?).

Utworzenie zdalnego klienta - RemovableClient

Utworzymy zdalnego klienta RemovableClient zgodnie z wytycznymi zdalnego dostępu do ziaren uruchomionych w ramach GlassFish - How do I access a Remote EJB from a stand-alone java client?.

Rozpoczynamy od zdefiniowania zależności od podprojektu - ejb3-remove-stateless-ejb3 (klient korzysta z klas podprojektu) przez Libraries > Add Library....

Grafika:ejb3-remove-stateless-addlibrary-ejb3.png

Po dodaniu zależności może pojawić się komunikat błędu związany z niedostępnością zależności w lokalnym repozytorium.

Grafika:ejb3-remove-stateless-showproblems.png

Do jego rozwiązania konieczne jest zbudowanie ziarna EJB - Removable, tj. zbudowanie projektu ejb3-remove-stateless-ejb3, a to z kolei umieści bibliotekę w lokalnym repozytorium m2. Projekt budujemy poprzez Build w menu pod prawym przyciskiem myszy po zaznaczeniu projektu ejb3-remove-stateless-ejb3.

Grafika:ejb3-remove-stateless-CleanAndBuild.png

Poprawne wykonanie polecenia oraz odświeżenie projektu klienta ejb3-remove-stateless-client (menu Reload Project) rozwiązuje problem.

Kolejnym krokiem jest stworzenie klasy klienta - pl.jaceklaskowski.javaee.RemovableClient, która jest niezwykle prosta, wręcz trywialna. Prostotę klienta zawdzięczamy bibliotece GlassFish - appserv-rt.jar, która zawiera konfigurację JNDI. Na uwagę zasługuje również wykorzystanie nazwy ziarna EJB - Removable - która została zdefiniowana w klasie ziarna przez adnotację @Stateless atrybut mappedName.

package pl.jaceklaskowski.javaee;

import javax.naming.Context;
import javax.naming.InitialContext;

public class RemovableClient {
    public static void main(String[] args) throws Exception {
        Context ctx = new InitialContext();
        RemovableRemote removable = (RemovableRemote) ctx.lookup("Removable");
        removable.remove("Look at the server's log");
    }
}

Konieczne jest dodanie zależności projektowych będących bibliotekami należącymi do serwera GlassFish - appserv-rt.jar oraz javaee.jar o zasięgu system (zgodnie z System Dependencies). Uwaga na zależność od systemu plików, gdzie zainstalowano GlassFish poprzez zmienną glassfish.home (domyślnie: c:/apps/glassfish).

Plik pom.xml przyjmuje następującą postać:

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <parent>
        <artifactId>ejb3-remove-stateless</artifactId>
        <groupId>pl.jaceklaskowski.javaee</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <groupId>pl.jaceklaskowski.javaee</groupId>
    <artifactId>ejb3-remove-stateless-client</artifactId>
    <name>ejb3-remove-stateless-client</name>
    <version>1.0</version>
    <properties>
        <glassfish.home>c:/apps/glassfish</glassfish.home>
    </properties>
    <dependencies>
        <dependency>
            <groupId>glassfish</groupId>
            <artifactId>appserv-rt.jar</artifactId>
            <version>LATEST</version>
            <scope>system</scope>
            <systemPath>${glassfish.home}/lib/appserv-rt.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>glassfish</groupId>
            <artifactId>javaee.jar</artifactId>
            <version>LATEST</version>
            <scope>system</scope>
            <systemPath>${glassfish.home}/lib/javaee.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>pl.jaceklaskowski.javaee</groupId>
            <artifactId>ejb3-remove-stateless-ejb3</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <includes>
                        <include>none</include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <id>run RemovableClientTest</id>
                        <phase>integration-test</phase>
                        <configuration>
                            <includes>
                                <include>**/RemovableClientTest.java</include>
                            </includes>
                        </configuration>
                        <goals>
                            <goal>test</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Ciekawostką konfiguracji modułu jest konfiguracja wtyczki maven-surefire-plugin, która skonfigurowana jest w ten sposób, aby domyślnie wykonywać wyłącznie testy spełniające wzorzec none (co w naszym przypadku znaczy, że żaden z testów nie zostanie wykonany, ponieważ wzorzec nie jest spełniony przez żaden z testów, a dokładniej przez nasz pojedyńczy test RemovableClientTest), a jedynie jego wykonanie podczas wykonania fazy integration-test.

Mimo dostępności klasy klienta automatyczne uruchomienie z poziomu m2 wykonamy za pomocą testu jednostkowego - RemovableClientTest (klasa pl.jaceklaskowski.javaee.RemovableClientTest), który tworzymy w węźle Test Packages.

package pl.jaceklaskowski.javaee;

import javax.naming.Context;
import javax.naming.InitialContext;
import org.junit.Test;

public class RemovableClientTest {

    @Test
    public void runRemoteClient() throws Exception {
        Context ctx = new InitialContext();
        RemovableRemote removable = (RemovableRemote) ctx.lookup("Removable");
        removable.remove("Look at the server's log");
    }
}

Klasa testu nie będzie domyślnie uruchomiona (patrz: konfiguracja wtyczki maven-surefire-plugin w pom.xml) ze względu na konieczność uruchomienia ziarna na serwerze aplikacyjnym GlassFish zanim test zostanie uruchmiony (gdyby ustawić wykonywanie testów, wtedy nie zostałby zbudowany projekt i niemożliwe byłoby zainstalowanie ziarna na serwerze).

Uruchomienie

Uruchomienie projektu polega na zainstalowaniu ziarna Removable na serwerze GlassFish, a następnie uruchomienie testu jednostkowego RemovableClientTest.

Uruchomienie serwera GlassFish

Zanim podejdziemy do instalacji ziarna, uruchomimy GlassFisha poleceniem asadmin start-domain (wcześniej zdefiniowałem zmienną PATH tak, aby zawierała katalog bin GlassFisha).

$ asadmin.bat start-domain domain1
Starting Domain domain1, please wait.
Log redirected to c:\apps\glassfish\domains\domain1\logs\server.log.
Redirecting output to C:/apps/glassfish/domains/domain1/logs/server.log
Domain domain1 is ready to receive client requests. Additional services are being started in background.
Domain [domain1] is running [Sun Java System Application Server 9.1 (build b58d-fcs)] with its configuration and logs at: [c:\apps\glassfish\domains].
Admin Console is available at [http://localhost:4848].
Use the same port [4848] for "asadmin" commands.
User web applications are available at these URLs:
[http://localhost:8080 https://localhost:8181 ].
Following web-contexts are available:
[/web1  /__wstx-services ].
Standard JMX Clients (like JConsole) can connect to JMXServiceURL:
[service:jmx:rmi:///jndi/rmi://dev:8686/jmxrmi] for domain management purposes.
Domain listens on at least following ports for connections:
[8080 8181 4848 3700 3820 3920 8686 ].
Domain does not support application server clusters and other standalone instances.

Instalacja ziarna EJB - Removable

Instalację ziarna należałoby poprzedzić jego zbudowaniem, jednakże podczas definiowania zależności klienta (projekt ejb3-remove-stateless-client) krok ten został już faktycznie wykonany. W katalogu target projektu ejb3-remove-stateless-ejb3 znajduje się plik ejb3-remove-stateless-ejb3-1.0.jar, który reprezentuje plik dystrybucyjny ziarna. Będzie on zainstalowany za pomocą polecenia asadmin deploy.

$ asadmin.bat deploy --user admin ejb3-remove-stateless-ejb3/target/ejb3-remove-stateless-ejb3-1.0.jar
Command deploy executed successfully.

W konsoli administracyjnej serwera (domyślnie dostępna pod adresem http://localhost:4848) weryfikujemy poprawną instalację ziarna EJB - Removable.

Grafika:ejb3-remove-stateless-console.png

Uruchomienie zdalnego klienta - RemovableClient

Rozpoczynamy od zbudowania całego projektu ejb3-remove-stateless uruchamiając menu Build.

Grafika:ejb3-remove-stateless-Build.png

Poprawne uruchomienie polecenia zakończy się następującym komunikatem:

...
------------------------------------------------------------------------
Reactor Summary:
------------------------------------------------------------------------
ejb3-remove-stateless ................................. SUCCESS [1.500s]
ejb3-remove-stateless-ejb3 ............................ SUCCESS [0.610s]
ejb3-remove-stateless-client .......................... SUCCESS [0.921s]
------------------------------------------------------------------------
------------------------------------------------------------------------
BUILD SUCCESSFUL
------------------------------------------------------------------------
...

Oczywiście wybierając menu Build, uruchamiamy etap install w nomenklaturze m2, co jednocześnie oznacza wykonanie etapu integration-test, a tym samym i naszego testu.

Bezpośrednie uruchomienie klienta to faktycznie wykonanie polecenia mvn integration-test, co w naszym przypadku korzystając z NetBeans IDE 6.0 jako narzędzia graficznego do uruchamiania poleceń m2 - sprowadza się do uruchomienia menu Custom > Goals... pod prawym przyciskiem myszki w projekcie ejb3-remove-stateless-client.

Lista etapów m2 znajduje się na stronie Build Lifecycle Phases.

Grafika:ejb3-remove-stateless-customgoals.png

a następnie wykonania etapu integration-test.

Grafika:ejb3-remove-stateless-runmaven-it.png

Poprawne uruchomienie testu kończy się komunikatami w NetBeans IDE:

...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running pl.jaceklaskowski.javaee.RemovableClientTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.203 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
------------------------------------------------------------------------
BUILD SUCCESSFUL
------------------------------------------------------------------------
...

oraz w dzienniku zdarzeń GlassFisha (plik domains/domain1/logs/server.log w katalogu domowym GlassFisha)

[#|2007-09-03T22:41:59.750+0200|INFO|sun-appserver9.1|javax.enterprise.system.stream.out|_ThreadID=16;_ThreadName=p: thread-pool-1; w: 8;|
Look at the server's log|#]

Mimo poprawnego uruchomienia klienta zdalnego nie można nazwać tego ostatecznym rozwiązaniem. Wymaga ono uruchomienia testu po zainstalowaniu ziarna i gdyby spróbować wykonać menu Build przed jego instalacją otrzymalibyśmy błąd. Propozycje usprawnień mile widziane. Kolejna odsłona artykułu z serwerem Apache OpenEJB 3.0-SNAPSHOT.

Osobiste