Uruchomienie ziarna MDB w Apache Geronimo

Z Jacek Laskowski - Wiki Projektanta Java EE

Ostatnio wiele czasu spędzałem z GlassFish v2 i NetBeans IDE 6, więc tym razem będzie trochę inaczej. Artykuł przedstawi tworzenie aplikacji składającej się z ziarna sterowanego komunikatami (ang. message-driven bean, dalej zwanym ziarnem MDB, lub po prostu MDB), które uruchomione będzie w ramach serwera aplikacji Java EE 5 - Apache Geronimo 2. W artykule przedstawiona zostanie konfiguracja zasobów związanych z infrastrukturą przesyłania komunikatów - kolejki JMS (ang. queue) oraz utworzenie zdalnego klienta, którego zadaniem będzie wysyłanie komunikatów do kolejki, na której nasłuchuje MDB.

Przykład bazuje na przykładzie opisanym w dokumentacji Apache Geronimo - JMS and MDB sample application.

Spis treści

Oprogramowanie

Środowisko składa się z następujących elementów:

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.

Kompletny projekt do zaimportowania do NetBeans IDE 6.0 dostępny jest jako ejb3-mdb-geronimo.zip.

Ziarno MDB - TicketServiceBean

Potyczki z tematem komunikacji asynchronicznej w Java EE 5 w wykonaniu ziarna MDB zaczniemy właśnie od jego utworzenia. Prostota tworzenia ziaren dowolnego typu jest nie do przecenienia i sprowadza się do stworzenia pojedyńczej klasy z odpowiednimi adnotacjami.

package pl.jaceklaskowski.mdb;

import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(
    activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "destination", propertyValue = "TicketQueue")
    }
)
public class TicketServiceBean implements MessageListener {

    @Resource
    private MessageDrivenContext ctx;
    
    private final Logger logger = Logger.getLogger(getClass().getName());

    public void onMessage(Message message) {
        TextMessage msg = null;
        try {
            if (message instanceof TextMessage) {
                msg = (TextMessage) message;
                logger.info("Odebrano wiadomość: " + msg.getText());
            } else {
                logger.info("Wiadomość niewłaściwego typu: " + message.getClass().getName());
            }
        } catch (JMSException e) {
            e.printStackTrace();
            ctx.setRollbackOnly();
        } catch (Throwable te) {
            te.printStackTrace();
        }
    }

    @PostConstruct
    void initialize() {
        logger.info("Wykonano PostConstruct - kontekst przekazany (wstrzelony)? " + (ctx != null));
    }

    @PreDestroy
    void destroy() {
        logger.info("Wykonano PreDestroy");
    }

    @AroundInvoke
    Object sprawdzKtoWykonujeMetodeBiznesowa( InvocationContext invocationContext) throws Exception {
        logger.info("Metoda przechwytująca wykonana - wywołujący: " + ctx.getCallerPrincipal().getName());
        return invocationContext.proceed();
    }
}

Ziarno MDB jest klasą udekorowaną adnotacją @MessageDriven, która (zazwyczaj) implementuje interfejs komunikacyjny reprezentujący usługę Java Message Service (JMS) - javax.jms.MessageListener (domyślny interfejs komunikacyjny, który jest dostępny we wszystkich serwerach aplikacji Java EE 5).

Poza wymaganymi adnotacjami ziarno korzysta z dodatkowych metod wykonywanych przy zmianie stadiów rozwojowych podczas jego działania. Wykonanie @PostConstruct następuje po utworzeniu egzemplarza ziarna i ustawieniu (wstrzeleniu) wszystkich zależności (w tym przypadku jedynie kontekst ziarna MessageDrivenContext), ale przed wykonaniem metody onMessage. Metoda udekorowana adnotacją @PreDestroy wykonywana jest przed usunięciem egzemplarza encji przez Geronimo. Metoda @AroundInvoke jest wykonywana każdorazowo przed wykonaniem metody onMessage i wyłącznie invocationContext.proceed() faktycznie spowoduje wykonanie tej metody.

Deskryptor ziarna MDB - openejb-jar.xml

Deskryptor ziarna MDB - openejb-jar.xml - definiuje mapowanie między zasobami wirtualnymi, z których korzysta ziarno, a zasobami fizycznymi dostarczanymi przez serwer aplikacji (sekcja message-driven w enterprise-beans). Mapowanie jest specyficzne dla każdego serwera aplikacji i dostarczane jest przez osobny plik w katalogu META-INF.

Pozostałe elementy pliku upraszczają zarządzanie ziarnem z perspektywy administratora Apache Geronimo i nadają mu określoną nazwę oraz określają jego zależności.

<?xml version="1.0" encoding="UTF-8"?>
<openejb-jar 
    xmlns="http://www.openejb.org/xml/ns/openejb-jar-2.1"
    xmlns:naming="http://geronimo.apache.org/xml/ns/naming-1.1" 
    xmlns:security="http://geronimo.apache.org/xml/ns/security-1.1" 
    xmlns:sys="http://geronimo.apache.org/xml/ns/deployment-1.2">
    <environment xmlns="http://geronimo.apache.org/xml/ns/deployment-1.2">
        <moduleId>			
            <groupId>pl.jaceklaskowski.mdb</groupId>
            <artifactId>TicketServiceBean</artifactId>
            <version>1.0</version>
            <type>car</type>
        </moduleId>
        <dependencies>
            <dependency>
                <groupId>org.apache.geronimo.configs</groupId>
                <artifactId>activemq-broker</artifactId>
                <type>car</type>
            </dependency>
        </dependencies>
        <hidden-classes/>
        <non-overridable-classes/>
    </environment>
    <enterprise-beans>
        <message-driven>
            <ejb-name>TicketServiceBean</ejb-name>
            <resource-adapter>
                <resource-link>jms-resources</resource-link>
            </resource-adapter>
        </message-driven>
    </enterprise-beans>
</openejb-jar>

Należy zwrócić uwagę na wartość elemenu resource-link, który jest nazwą symboliczną dla zasobów definiowanych w kolejnym pliku geronimo-application.xml.

Deskryptor aplikacji EAR - geronimo-application.xml

Istnieje kilka sposobów utworzenia fizycznych zasobów JMS w Apache Geronimo, z których będzie korzystało ziarno i jedną z nich jest zadeklarowanie ich utworzenia w pliku geronimo-application.xml. Plik geronimo-application.xml jest plikiem konfiguracji aplikacji przemysłowej (EAR) specyficznym dla serwera Geronimo. W nim deklarujemy nazwę dla aplikacji (sekcja environment) oraz zewnętrzne moduły, z których korzysta aplikacja (sekcja ext-module).

<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://geronimo.apache.org/xml/ns/j2ee/application-2.0">
    <environment xmlns="http://geronimo.apache.org/xml/ns/deployment-1.2">
        <moduleId>
            <groupId>pl.jaceklaskowski.mdb</groupId>
            <artifactId>TicketServiceEAR</artifactId>
            <version>1.0</version>
            <type>ear</type>
        </moduleId>
    </environment>
    <ext-module>
        <connector>jms-resources</connector>
        <external-path xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.2">
            <dep:groupId>org.apache.geronimo.modules</dep:groupId>
            <dep:artifactId>geronimo-activemq-ra</dep:artifactId>
            <dep:version>2.1-SNAPSHOT</dep:version>
            <dep:type>rar</dep:type>
        </external-path>
        <connector xmlns="http://geronimo.apache.org/xml/ns/j2ee/connector-1.2">
            <dep:environment xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.2">
                <dep:moduleId>
                    <dep:groupId>org.apache.geronimo.samples</dep:groupId>
                    <dep:artifactId>jms-resources</dep:artifactId>
                    <dep:version>1.2</dep:version>
                    <dep:type>rar</dep:type>
                </dep:moduleId>
                <dep:dependencies>
                    <dep:dependency>
                        <dep:groupId>org.apache.geronimo.configs</dep:groupId>
                        <dep:artifactId>activemq-broker</dep:artifactId>
                        <dep:type>car</dep:type>
                    </dep:dependency>
                </dep:dependencies>
            </dep:environment>
            <resourceadapter>
                <resourceadapter-instance>
                    <resourceadapter-name>jms-resources</resourceadapter-name>
                    <workmanager xmlns="http://geronimo.apache.org/xml/ns/naming-1.2">
                        <gbean-link>DefaultWorkManager</gbean-link>
                    </workmanager>
                </resourceadapter-instance>
                <outbound-resourceadapter>
                    <connection-definition>
                        <connectionfactory-interface>javax.jms.ConnectionFactory</connectionfactory-interface>
                        <connectiondefinition-instance>
                            <name>TicketConnectionFactory</name>
                            <implemented-interface>javax.jms.QueueConnectionFactory</implemented-interface>
                            <implemented-interface>javax.jms.TopicConnectionFactory</implemented-interface>
                            <connectionmanager>
                                <xa-transaction>
                                    <transaction-caching/>
                                </xa-transaction>
                                <single-pool>
                                    <match-one/>
                                </single-pool>
                            </connectionmanager>
                        </connectiondefinition-instance>
                    </connection-definition>
                </outbound-resourceadapter>
            </resourceadapter>
            <adminobject>
                <adminobject-interface>javax.jms.Queue</adminobject-interface>
                <adminobject-class>org.apache.activemq.command.ActiveMQQueue</adminobject-class>
                <adminobject-instance>
                    <message-destination-name>TicketQueue</message-destination-name>
                    <config-property-setting name="PhysicalName">TicketQueue</config-property-setting>
                </adminobject-instance>
            </adminobject>
        </connector>
    </ext-module>
</application>

Element resourceadapter-name wyznacza nazwę dla grupy zasobów definiowanych przez connectiondefinition-instance oraz adminobject-instance. Wartość elementu resourceadapter-name musi odpowiadać nazwie z pliku openejb-jar.xml.

Instalacja ziarna MDB - TicketServiceBean

Ziarno MDB jest częścią aplikacji przemysłowej TicketServiceEAR.ear. Instalacja aplikacji to faktycznie instalacja ziarna MDB oraz utworzenie zasobów JMS niezbędne do jego poprawnego uruchomienia.

Uruchomienie instalacji sprowadza się do wykonania skrytu deploy Apache Geronimo, który znajduje się w podkatalogu bin w katalogu instalacyjnym serwera. Rozpoczniejmy jednak od uruchomienia Geronimo.

jlaskowski@dev /cygdrive/c/geronimo
$ ./bin/geronimo.sh run
Using GERONIMO_BASE:   c:\geronimo
Using GERONIMO_HOME:   c:\geronimo
Using GERONIMO_TMPDIR: c:\geronimo\var\temp
Using JRE_HOME:        c:\apps\java5\jre
Booting Geronimo Kernel (in Java 1.5.0_11)...
Starting Geronimo Application Server v2.1-SNAPSHOT
...
  Listening on Ports:
       0 0.0.0.0   Derby Connector
    1050 127.0.0.1 CORBA Naming Service
    1099 0.0.0.0   RMI Naming
    2001 127.0.0.1 OpenEJB ORB Adapter
    4201 0.0.0.0   OpenEJB Daemon
    6882 127.0.0.1 OpenEJB ORB Adapter
    8009 0.0.0.0   Jetty Connector AJP13
    8080 0.0.0.0   Jetty SelectChannel Connector HTTP
    8443 0.0.0.0   Jetty SelectChannel Connector HTTPS
    9999 0.0.0.0   JMX Remoting Connector
   61613 0.0.0.0   ActiveMQ Transport Connector
   61616 0.0.0.0   ActiveMQ Transport Connector

  Started Application Modules:
    EAR: org.apache.geronimo.configs/uddi-jetty6/2.1-SNAPSHOT/car
    EAR: org.apache.geronimo.plugins/console-jetty/2.1-SNAPSHOT/car
    JAR: org.apache.geronimo.configs/mejb/2.1-SNAPSHOT/car
    RAR: org.apache.geronimo.configs/activemq-ra/2.1-SNAPSHOT/car
    RAR: org.apache.geronimo.configs/system-database/2.1-SNAPSHOT/car
    WAR: org.apache.geronimo.configs/ca-helper-jetty/2.1-SNAPSHOT/car
    WAR: org.apache.geronimo.configs/dojo-jetty6/2.1-SNAPSHOT/car
    WAR: org.apache.geronimo.configs/remote-deploy-jetty/2.1-SNAPSHOT/car
    WAR: org.apache.geronimo.configs/welcome-jetty/2.1-SNAPSHOT/car
    WAR: org.apache.geronimo.plugins/activemq-jetty/2.1-SNAPSHOT/car
    WAR: org.apache.geronimo.plugins/debugviews-jetty/2.1-SNAPSHOT/car
    WAR: org.apache.geronimo.plugins/plancreator-jetty/2.1-SNAPSHOT/car
    WAR: org.apache.geronimo.plugins/system-database-jetty/2.1-SNAPSHOT/car

  Web Applications:
    /
    /CAHelper
    /activemq
    /console
    /console-base
    /debug-views
    /dojo
    /juddi
    /plan-creator
    /remote-deploy
    /system-database

Geronimo Application Server started

Teraz możemy przystąpić do instalacji aplikacji.

jlaskowski@dev /cygdrive/c/geronimo
$ bin/deploy.sh -u system -p manager deploy TicketServiceEAR.ear
Using GERONIMO_BASE:   c:\geronimo
Using GERONIMO_HOME:   c:\geronimo
Using GERONIMO_TMPDIR: c:\geronimo\var\temp
Using JRE_HOME:        c:\apps\java5\jre
    Deployed pl.jaceklaskowski.mdb/TicketServiceEAR/1.0/ear
      `-> TicketServiceMDB.jar
      `-> jms-resources

Klient zdalny - TicketServiceClient

Dla sprawdzenia poprawności instalacji ziarna MDB i utworzenia wymaganych zasobów JMS skorzystamy ze zdalnego klienta TicketServiceClient.

package pl.jaceklaskowski.mdb;

import java.util.Properties;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;

public class TicketServiceClient {

    public static void main(String[] args) throws Exception {
        //
        // Krok 1 - Połączenie z serwerem Geronimo
        //
        Properties env = new Properties();
        env.put(Context.PROVIDER_URL, "tcp://localhost:61616");
        env.put("connectionFactoryNames", "TicketConnectionFactory");
        env.put("queue.TicketQueue", "TicketQueue");
        Context jndiContext = new InitialContext(env);

        QueueConnectionFactory factory = (QueueConnectionFactory) jndiContext.lookup("TicketConnectionFactory");
        Queue queue = (Queue) jndiContext.lookup("TicketQueue");
        //
        // Krok 2 - Utworzenie połączeń z zasobami JMS
        //
        QueueConnection connection = factory.createQueueConnection();
        QueueSession session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
        MessageProducer producer = session.createProducer(queue);
        //
        // Krok 3 - Wysłanie wiadomości
        //
        for (int i = 0; i < 5; i++) {
            System.out.println("Wysylana wiadomosc nr #" + i);
            TextMessage txtMsg = session.createTextMessage();
            txtMsg.setText("Wiadomosc od Jacka");
            producer.send(txtMsg);
        }
        //
        // Krok 4 - Zakończenie aplikacji - sprzątanie
        //
        session.close();
        connection.close();
    }
}

Aplikacja jest zwykłą aplikacją Java, której celem jest wyszukanie zasobów zdalnych uruchomionych w ramach serwera Geronimo (krok 1 i 2) i wysłanie wiadomości (krok 3), aby na zakończenie posprzątać po sobie (krok 4).

Istotną kwestią jest konfiguracja usługi JNDI (krok 1), gdzie:

  • Context.PROVIDER_URL ma wartość tcp://localhost:61616 określającą adres serwera Apache ActiveMQ uruchomionego w ramach Geronimo
  • connectionFactoryNames musi wskazywać na nazwę fabryki, jaką będziemy wykorzystywać w aplikacji (która de facto musi odpowiadać tej zdefiniowanej dla ziarna MDB, w pliku openejb-jar.xml)
  • queue.TicketQueue - podobnie jak w connectionFactoryNames wskazuje na nazwę zasobu zdefiniowanego dla ziarna MDB w pliku openejb-jar.xml

Do poprawnej kompilacji i uruchomienia aplikacji wymagane są biblioteki projektu Apache ActiveMQ, który jest domyślnym dostawcą JMS dla Geronimo. Z pomocą jeszcze innego projektu Apache Maven, który weźmie na siebie odpowiedzialność pobrania wymaganych bibliotek.

Plik pom.xml projektu klienta zdalnego jest następujący:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>pl.jaceklaskowski.mdb</groupId>
    <artifactId>ticket-service-client</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <name>TicketServiceClient</name>
    <url>http://www.JacekLaskowski.pl/wiki/Uruchomienie_ziarna_MDB_w_Apache_Geronimo</url>
    <dependencies>
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>apache-activemq</artifactId>
            <version>4.1.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>pl.jaceklaskowski.mdb.TicketServiceClient</mainClass>
                            <addClasspath>true</addClasspath>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <executable>java</executable>
                    <arguments>
                        <argument>-classpath</argument>
                        <classpath/>
                        <argument>pl.jaceklaskowski.mdb.TicketServiceClient</argument>
                    </arguments>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Uruchomienie klienta zdalnego - TicketServiceClient

Uruchomienie TicketServiceClient sprowadza się do uruchomienia polecenia mvn compile exec:exec.

jlaskowski@dev /cygdrive/c/TicketServiceClient-M2
$ mvn compile exec:exec
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'exec'.
[INFO] ----------------------------------------------------------------------------
[INFO] Building TicketServiceClient
[INFO]    task-segment: [compile, exec:exec]
[INFO] ----------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to c:\TicketServiceClient-M2\target\classes
[INFO] [exec:exec]
[INFO] Wysylana wiadomosc nr #0
[INFO] Wysylana wiadomosc nr #1
[INFO] Wysylana wiadomosc nr #2
[INFO] Wysylana wiadomosc nr #3
[INFO] Wysylana wiadomosc nr #4
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4 seconds
[INFO] Finished at: Sun Oct 21 22:32:40 CEST 2007
[INFO] Final Memory: 4M/254M
[INFO] ------------------------------------------------------------------------

Na konsoli Geronimo powinny pojawić się następujące komunikaty świadczące o poprawnej konfiguracji środowiska dla ziarna MDB i wykonania jego metod - @PostConstruct, @AroundInvoke oraz onMessage (metoda @PreDestroy zostanie wykonana podczas zatrzymania serwera lub odinstalowania aplikacji).

2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean initialize
INFO: Wykonano PostConstruct - kontekst przekazany (wstrzelony)? true
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean initialize
INFO: Wykonano PostConstruct - kontekst przekazany (wstrzelony)? true
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean initialize
INFO: Wykonano PostConstruct - kontekst przekazany (wstrzelony)? true
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean initialize
INFO: Wykonano PostConstruct - kontekst przekazany (wstrzelony)? true
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean initialize
INFO: Wykonano PostConstruct - kontekst przekazany (wstrzelony)? true
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean sprawdzKtoWykonujeMetodeBiznesowa
INFO: Metoda przechwytująca wykonana - wywołujący: Unauthenticated
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean sprawdzKtoWykonujeMetodeBiznesowa
INFO: Metoda przechwytująca wykonana - wywołujący: Unauthenticated
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean sprawdzKtoWykonujeMetodeBiznesowa
INFO: Metoda przechwytująca wykonana - wywołujący: Unauthenticated
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean sprawdzKtoWykonujeMetodeBiznesowa
INFO: Metoda przechwytująca wykonana - wywołujący: Unauthenticated
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean sprawdzKtoWykonujeMetodeBiznesowa
INFO: Metoda przechwytująca wykonana - wywołujący: Unauthenticated
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean onMessage
INFO: Odebrano wiadomość: Wiadomosc od Jacka
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean onMessage
INFO: Odebrano wiadomość: Wiadomosc od Jacka
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean onMessage
INFO: Odebrano wiadomość: Wiadomosc od Jacka
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean onMessage
INFO: Odebrano wiadomość: Wiadomosc od Jacka
2007-10-21 22:32:40 pl.jaceklaskowski.mdb.TicketServiceBean onMessage
INFO: Odebrano wiadomość: Wiadomosc od Jacka
Osobiste