JBoss AS 7 – Remote JMS queue

Today I’d like to post a quick, laconic, and hopefully 100% correct sample of remote JMS queue utilization in JBoss 7 AS. Diagram posted below shows the target environment where one application server instance hosts JMS queue, and the other one acts as message producer and consumer.

Remote JMS Environment

JBoss2 (queue owner) configuration:

<hornetq-server>
   <security-domain>hornetq-security-domain</security-domain>
   ...
   <jms-destinations>
      <jms-queue name="RemoteQueue1">
         <entry name="queue/RemoteQueue1"/>
         <entry name="java:jboss/exported/jms/queue/RemoteQueue1"/>
      </jms-queue>
   </jms-destinations>
</hornetq-server>
...
<security-domains>
   ...
   <security-domain name="hornetq-security-domain" cache-type="default">
      <authentication>
         <login-module code="UsersRoles" flag="required">
            <module-option name="usersProperties" value="/opt/jboss3/standalone/configuration/jms-users.properties"/>
            <module-option name="rolesProperties" value="/opt/jboss3/standalone/configuration/jms-roles.properties"/>
         </login-module>
       </authentication>
   </security-domain>
</security-domains>
[jboss@jmsenv Desktop]$ cat /opt/jboss3/standalone/configuration/jms-users.properties
jmsuser=jmspassword
[jboss@jmsenv Desktop]$ cat /opt/jboss3/standalone/configuration/jms-roles.properties
jmsuser=guest

The security related entries are not required if you decide to assign unauthorized users to “guest” group (which is by default allowed to send and receive messages). This can be achieved by adding <module-option name="unauthenticatedIdentity" value="guest"/> to login module configuration.

JBoss1 (queue user) configuration:

<hornetq-server>
   ...
   <connectors>
      <connector name="remote-jms-nonmgmt">
         <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class>
         <param key="host" value="192.168.117.159"/>
         <param key="port" value="5445"/>
      </connector>
      <netty-connector name="remote-jms-mgmt" socket-binding="remote-jms-binding"/>
   </connectors>
   <jms-connection-factories>
      ...
      <connection-factory name="ConnectionFactory1NonMgmt">
         <connectors>
            <connector-ref connector-name="remote-jms-nonmgmt"/>
         </connectors>
         <entries>
            <entry name="java:/ConnectionFactory1NonMgmt"/>
         </entries>
      </connection-factory>
      <pooled-connection-factory name="ConnectionFactory1Mgmt">
         <user>jmsuser</user>
         <password>jmspassword</password>
         <connectors>
            <connector-ref connector-name="remote-jms-mgmt"/>
         </connectors>
         <entries>
            <entry name="java:/ConnectionFactory1Mgmt"/>
         </entries>
      </pooled-connection-factory>
   </jms-connection-factories>
</hornetq-server>
...
<socket-binding-group name="full-ha-sockets" default-interface="public">
   ...
   <outbound-socket-binding name="remote-jms-binding">
      <remote-destination host="192.168.117.159" port="5445"/>
   </outbound-socket-binding>
</socket-binding-group>

Producer code:

@Resource(mappedName = "java:/ConnectionFactory1Mgmt")
private ConnectionFactory connectionFactory;

public void sendMessage(String text) {
   Connection connection = connectionFactory.createConnection();
   Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );
   Queue queue = session.createQueue( "RemoteQueue1" );
   MessageProducer producer = session.createProducer( queue );
   connection.start();
   TextMessage message = session.createTextMessage();
   message.setText( text );
   producer.send( message );
   session.close();
   connection.close();
}

Message-Driven Bean consumer code:

import org.jboss.ejb3.annotation.ResourceAdapter;

@ResourceAdapter("ConnectionFactory1Mgmt")
@MessageDriven(
   name="ConsumerMdb",
   activationConfig = {
      @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
      @ActivationConfigProperty(propertyName="destination", propertyValue="queue/RemoteQueue1")
})
public class ConsumerMdb implements MessageListener {
   ...
}

Here you might need to import org.jboss.ejb3:jboss-ejb3-ext-api:2.0.0 library into you project, but don’t forget to mark it as provided by the runtime environment.

Manually consuming messages from EJB (inside container):

InitialContext context = new InitialContext();
ConnectionFactory connectionFactory = (ConnectionFactory) context.lookup( "ConnectionFactory1NonMgmt" );
Connection connection = connectionFactory.createConnection( "jmsuser", "jmspassword" );
Session session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );
Queue queue = session.createQueue( "RemoteQueue1" );
MessageConsumer consumer = session.createConsumer( queue );
connection.start();
Message message = consumer.receive();

General notes:

  1. Use <pooled-connection-factory> for MDBs and message producers deployed inside container.
  2. Use <connection-factory> for non-managed producers and consumers (including the remote once).

Reading:

  1. JBoss forum thread: https://community.jboss.org/message/722711.
  2. Justin Bertram explaining my doubts (thanks!): https://community.jboss.org/message/780778.
Advertisements

Errata: Biblioteki zewnętrzne, EAR i Maven

Pierwsza errata na moim blogu :). Umiem przyznać się do błędu, lecz miejmy nadzieję, że to jedna z ostatnich tego typu sytuacji. Rzecz dotyczy dodawania bibliotek zewnętrznych do pakietu EAR zbudowanego przy użyciu Maven. Poniżej zamieszczam najbardziej elegancką według mnie konfigurację maven-ear-plugin:

<plugin>
  <artifactId>maven-ear-plugin</artifactId>
  <version>2.4.2</version>
  <configuration>
    <jboss>
      <version>5</version>
    </jboss>
    <defaultLibBundleDir>lib</defaultLibBundleDir>
    <modules>
      <ejbModule>
        <groupId>javaee-app.controller</groupId>
        <artifactId>controller</artifactId>
      </ejbModule>
      <webModule>
        <groupId>javaee-app.view</groupId>
        <artifactId>view</artifactId>
        <contextRoot>view</contextRoot>
      </webModule>
      <jarModule>
        <groupId>javaee-app.model</groupId>
        <artifactId>model</artifactId>
        <includeInApplicationXml>true</includeInApplicationXml>
        <bundleDir>/</bundleDir>
      </jarModule>
      <jarModule>
        <groupId>javaee-app.utils</groupId>
        <artifactId>utils</artifactId>
        <includeInApplicationXml>true</includeInApplicationXml>
        <bundleDir>/</bundleDir>
      </jarModule>
      <jarModule>
        <groupId>org.springframework</groupId>
        <artifactId>spring</artifactId>
      </jarModule>
    </modules>
  </configuration>
</plugin>

Szczególną uwagę należy zwrócić na tag defaultLibBundleDir, dzięki któremu wszystkie biblioteki dodatkowe zostaną umieszczane domyślnie w katalogu lib (tutaj Spring Framework). Wszystkie moduły deploy’owane jako jarModule także znalazłyby się we wspomnianym wcześniej katalogu gdyby nie nadpisanie konfiguracji znacznikiem bundleDir. W tym wypadku należy jednak wymusić dodanie odpowiedniego wpisu do pliku application.xml przez ustawienie atrybutu includeInApplicationXml na true.

Dzięki przedstawionej wyżej konfiguracji dodatkowe biblioteki zostaną umieszczone w katalogu lib archiwum EAR oraz nie będą wymagały dodatkowego wpisu w głównym deskryptorze aplikacji.

Pragnę także oznajmić, że idąc z duchem czasu zamierzam przesiąść się ze starego, dobrego Subversion na Mercurial’a. Źródła projektu można pobrać z repozytorium: https://bitbucket.org/lukasz_antoniak/javaee-app.

EJB3 i Spring

Zapytano mnie pewnego razu, czy próbowałem połączyć Spring i EJB3. Podczas przygotowań do SCBCD nie chciałem tracić czasu na takie zabawy. Teraz jednak to co innego :). Wewnętrznie nie odczuwam potrzeby korzystania z dwóch kontenerów jednocześnie – lekkiego Spring’owego i JNDI. Zachęciła mnie jednak myśl o wykorzystaniu takich fragmentów API jak JdbcTemplate, które uprzyjemniają np. wywołanie procedur składowanych. Jeśli interesuje kogoś jednak współdzielenie bean’ów między kontenerami, to informacje na ten temat zamieściłem w dalszej części owego wpisu. Jako punkt początkowy development’u przykładowej aplikacji, przyjąłem końcową postać kodu źródłowego zamieszczonego w poście Budowanie aplikacji Java EE wykorzystując Apache Maven.

Dodanie zależności Spring do modułu Controller
Spring Framewrok jako bibliotekę zewnętrzną dodać można do projektu na kilka sposobów. Pierwszy, wykorzystany przeze mnie, polega na zdefiniowaniu nowego dependency w pliku pom.xml projektu Controller, a następnie uwzględnieniu modułu Spring’owego w konfiguracji maven-ear-plugin. Jeśli zdanie poprzednie nie wydaje Ci się jasne – odsyłam do wpisu na temat Java EE i Maven. Wybrane rozwiązanie nie jest według mnie eleganckie. W końcu od czego jest katalog lib modułu EJB w pliku EAR? Jedyne usprawiedliwienie pójścia tym tropem, to brak czasu i konieczność zajmowania się pracą magisterską… Inny sposób udostępnienia bibliotek zewnętrznych, to po prostu instalacja Spring Framework bezpośrednio na serwer aplikacyjny JBoss, a nie umieszczanie go wewnątrz pliku EAR.

Generalna konfiguracja Spring Framework
Dla (między innymi) standardu EJB, programiści Spring Framework stworzyli klasę BeanFactoryLocator, która odpowiada za załadowanie grupy ApplicationContext’ów. Domyślnie na classpath’ie szuka ona pliku beanRefContext.xml. W moim przypadku plik ten umieściłem w katalogu resources projektu Controller. Kod źródłowy wygląda następująco:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
  <context:annotation-config/>
  <bean id="applicationcontext">
    <constructor-arg>
      <list>
        <value>applicationContext.xml</value>
      </list>
    </constructor-arg>
  </bean>
</beans>

Linia 6 umożliwia wstrzykiwanie zależności przez adnotację @Autowired (dokumentacja Spring Framework 2.5.x http://static.springsource.org/spring/docs/2.5.x/reference/beans.html – podrozdział “3.11. Annotation-based configuration”). Reszta prezentowanego kodu odpowiada za wczytanie jednego bądź wielu plików konfiguracyjnych z bean’ami Spring’owymi. Referowany plik applicationContext.xml znaleźć się powinien w tym samym katalogu co beanRefContext.xml i na razie może nie definiować nowych ziaren.

JdbcTemplate
W celu wykorzystania Spring’owego JdbcTemplate stworzyłem po stronie bazy danych Oracle (schemat HR) nową procedurę składowaną:

CREATE OR REPLACE PROCEDURE add_job(p_job_id jobs.job_id%TYPE, p_job_title jobs.job_title%TYPE)
IS
BEGIN
   INSERT INTO jobs(job_id, job_title) VALUES(p_job_id, p_job_title);
END add_job;

W celu jej wywołania, wstrzyknąłem używane przez aplikację źródło danych tworząc przy tym nowy obiekt JdbcTemplate (zaleta setter injection). Kod odpowiedzialny za wstrzykiwanie zależności oraz wywołanie procedury add_job wyglądać powinien następująco:

public void addByPLSQLProcedure(Job job) {
   final String procedureName = "add_job";

   List<SqlParameter> declaredParameters = new ArrayList<SqlParameter>();
   declaredParameters.add(new SqlParameter("p_job_id", Types.VARCHAR));
   declaredParameters.add(new SqlParameter("p_job_title", Types.VARCHAR));

   Map parameterValues = new HashMap();
   parameterValues.put("p_job_id", job.getJobId() + "2");
   parameterValues.put("p_job_title", job.getJobTitle() + "2");

   SimpleJdbcCall call = new SimpleJdbcCall(jdbcTemplate);
   call.withProcedureName(procedureName);
   for (SqlParameter p : declaredParameters) {
      call.addDeclaredParameter(p);
   }
   call.execute(parameterValues);
}

@Resource(mappedName="java:/OracleDS")
public void setDataSource(DataSource dataSource) {
   this.jdbcTemplate = new JdbcTemplate(dataSource);
}

Spring IoC i JNDI
W celu prezentacji wstrzykiwania bean’ów między kontenerami, posłużę się dość prostym, lecz dzięki temu nie zaciemniającym istoty działania przykładem. Stworzę nowy pakiet edu.lantoniak.controller.di, gdzie lokalne ziarno EJB (BottomEJBBean) wstrzyknę do bean’a Spring’owego (MiddleSpringBean), a następnie tak skonstruowany obiekt z powrotem do innego komponentu EJB (TopEJBBean). W celu wstrzyknięci BottomEJBBean do MiddleSpringBean należy zdefiniować odpowiednie odwołanie w pliku applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
  <jee:local-slsb id="bottomEJBLocal" jndi-name="ear-1.0/BottomEJBBean/local" business-interface="edu.lantoniak.controller.di.BottomEJBLocal"/>
  <bean id="middleSpringBean">
    <property name="bottomEJBLocal" ref="bottomEJBLocal"/>
  </bean>
</beans>

Kluczowa linia 6 tworzy referencję do komponentu EJB w kontenerze Spring IoC. Ważnym atrybutem, którego wartość różnić się może dla poszczególnych serwerów aplikacyjnych jest jndi-name. Element ten dla JBoss 5 powinien mieć następującą postać: [nazwa_aplikacji]/[bean’s_global_name]. Polecam zapoznanie się z dokumentacją: http://static.springsource.org/spring/docs/2.5.x/reference/ejb.html.

Aby wstrzyknąć stworzone poprzednio ziarno Spring’owe do komponentu EJB, skorzystałem z interceptora SpringBeanAutowiringInterceptor oraz adnotacji @Autowired:

@Stateless
@Interceptors(SpringBeanAutowiringInterceptor.class)
public class TopEJBBean implements TopEJBRemote {
   @Autowired
   private MiddleSpringBean middleSpringBean = null;

   public double pow2(double x) {
      return Math.pow(x, middleSpringBean.getBottomEJBLocal().getOperand());
   }
}

Źródła projektu: javaee-app+spring.zip.
Inne ciekawe materiały: spring_ejb3_integration.pdf, spring-ejb3-integration-demo.zip, mbogoevici_1050_spring_on_jboss.pdf.
Errata: Biblioteki zewnętrzne, EAR i Maven.

Budowanie aplikacji Java EE wykorzystując Apache Maven

Wykonując ćwiczenia przygotowujące do SCBCD oparłem się na tworzeniu małych projektów Eclipse’owych oraz deploy’owaniu ich za pomocą wtyczki do obsługi serwera JBoss. Przyszedł jednak czas na odświeżenie i ugruntowanie wiadomości z zakresu Maven oraz sposobu pakowania archiwum EAR. Stworzę więc prosty projekt Java EE składający się z kilku modułów:

  • Model – przechowujący mapowanie ORM i pliki encji.
  • View – moduł widoku aplikacji opartym na JSF.
  • Controller – warstwa serwisów aplikacji wykorzystująca EJB.
  • Utils – pomocniczy moduł z często używanymi funkcjami.
  • EAR – projekt budujący archiwum EAR.

W celu stworzenia głównego katalogu projektu wydajemy z konsoli komendę:

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=javaee-app -DartifactId=javaee-app -Dversion=1.0

Następnie edytujemy plik pom.xml i zmieniamy sposób pakowania (tag packaging) na pom. Kolejna czynność polega na stworzeniu wszystkich modułów pomocniczych:

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=javaee-app.view -DartifactId=view -DpackageName=edu.lantoniak.view -Dversion=1.0
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=javaee-app.utils -DartifactId=utils -DpackageName=edu.lantoniak.utils -Dversion=1.0
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=javaee-app.controller -DartifactId=controller -DpackageName=edu.lantoniak.controller -Dversion=1.0
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=javaee-app.model -DartifactId=model -DpackageName=edu.lantoniak.model -Dversion=1.0
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=javaee-app.ear -DartifactId=ear -DpackageName=edu.lantoniak.ear -Dversion=1.0

Do pliku POM głównego projektu proponuję dodać wszystkie zależności związane z Javą EE, JPA oraz JSF. Plik ten w końcowej wersji powinien wyglądać następująco:

<?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>javaee-app</groupId>
   <artifactId>javaee-app</artifactId>
   <packaging>pom</packaging>
   <version>1.0</version>
   <name>javaee-app</name>
   <url>http://maven.apache.org</url>
   <dependencies>
      <dependency>
         <groupId>javaee</groupId>
         <artifactId>javaee-api</artifactId>
         <version>5</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>javax.persistence</groupId>
         <artifactId>persistence-api</artifactId>
         <version>1.0</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>javax.servlet</groupId>
         <artifactId>servlet-api</artifactId>
         <version>2.5</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>javax.faces</groupId>
         <artifactId>jsf-api</artifactId>
         <version>1.2</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>javax.faces</groupId>
         <artifactId>jsf-impl</artifactId>
         <version>1.2_13</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>javax.servlet.jsp</groupId>
         <artifactId>jsp-api</artifactId>
         <version>2.1</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>3.8.1</version>
         <scope>test</scope>
      </dependency>
   </dependencies>
   <modules>
      <module>view</module>
      <module>utils</module>
      <module>controller</module>
      <module>ear</module>
      <module>model</module>
   </modules>
</project>

Jeśli pobranie pewnych zależności kończy się niepowodzeniem, proponuję dodać następujące repozytorium Maven’a:

<repositories>
   <repository>
      <id>java.net1</id>
      <name>Java.Net Maven1 Repository, hosts the javaee-api dependency</name>
      <url>http://download.java.net/maven/1</url>
      <layout>legacy</layout>
   </repository>
</repositories>

Wracając jednak do pliku POM głównego projektu – na uwagę zasługuje opcja provided znacznika scope. Dzięki temu Maven, przy budowania EAR’a, nie umieszcza w nim bibliotek zainstalowanych na serwerze aplikacyjnym. Wewnątrz tagu modules wymienione zostały wszystkie moduły, z których składa się aplikacja. Zależności między modułami umieszcza się w plikach POM konkretnych modułów wraz z odniesieniem do parent’a, czyli projektu głównego (patrz do kodu źródłowego).

Moduł Controller
Moduł Controller korzysta z EJB, do którego zależność umieszczona została w głównym pliku POM projektu. W celu zbudowania projektu EJB, skorzystać należy z dodatku maven-ejb-plugin:

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

Minimalna konfiguracja wymaga jedynie wyspecyfikowanie używanej wersji EJB. Moduł Controller zależeć będzie od dwóch wewnętrznych modułów – Model i Utils (w końcu ciężko byłoby korzystać z JPA po stronie ziaren EJB bez klas encji):

<dependency>
   <groupId>javaee-app.model</groupId>
   <artifactId>model</artifactId>
   <version>1.0</version>
   <scope>compile</scope>
</dependency>
<dependency>
   <groupId>javaee-app.utils</groupId>
   <artifactId>utils</artifactId>
   <version>1.0</version>
   <scope>compile</scope>
</dependency>

Niestety oddzielenie modelu aplikacji (mapowania ORM) od warstwy EJB ciągnie za sobą dwie poważne wady. Deklaracja Persistence Unit pozostać musi po stronie modułu Controller’a, ponieważ zasięg widoczności PU nie wychodzi poza archiwum JAR, a umieszczenie korzenia PU bezpośrednio w archiwum EAR uznałem za gorszy pomysł. Druga bolączka to konieczność wyspecyfikowania nazwy archiwum JAR, w którym znajdą się klasy poszczególnych encji, w pliku persistence.xml (controller/main/resources/META-INF/persistence.xml, linijka 7):

<?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="javaee-appPU">
      <jta-data-source>java:/OracleDS</jta-data-source>
      <jar-file>model-1.0.jar</jar-file>
   </persistence-unit>
</persistence>

Moduł EAR
Moduł EAR odpowiada za stworzenia archiwum EAR (w tym za generację pliku application.xml, spakowanie pozostałych modułów etc.), które w dalszych krokach podlega instalacji na serwerze aplikacyjnym. Do budowy archiwum zastosuję plugin maven-ear-plugin:

<plugin>
   <artifactId>maven-ear-plugin</artifactId>
   <version>2.4.2</version>
   <configuration>
      <jboss>
         <version>5</version>
      </jboss>
      <modules>
         <ejbModule>
            <groupId>javaee-app.controller</groupId>
            <artifactId>controller</artifactId>
         </ejbModule>
         <webModule>
            <groupId>javaee-app.view</groupId>
            <artifactId>view</artifactId>
            <contextRoot>view</contextRoot>
         </webModule>
         <jarModule>
            <groupId>javaee-app.model</groupId>
            <artifactId>model</artifactId>
            <includeInApplicationXml>true</includeInApplicationXml>
         </jarModule>
         <jarModule>
            <groupId>javaee-app.utils</groupId>
            <artifactId>utils</artifactId>
            <includeInApplicationXml>true</includeInApplicationXml>
         </jarModule>
      </modules>
   </configuration>
</plugin>

Instrukcje wewnątrz znacznika modules specyfikują poszczególne moduły aplikacji wraz z ich typami (ejbModule, webModule, jarModule). Ponadto moduły Model i Utils powinny zostać uwzględnione w pliku application.xml (webModule i ejbModule są tam dodane domyślnie – odpowiednio w tagach web i ejb). Powoduje to konieczność ustawienia flagi includeInApplicationXml na true. Plugin maven-ear-plugin umożliwia także zmianę Context Root, pod którym deploy’owana jest aplikacji (linia 16).

Moduł Model, Utils i View
Konfiguracja Maven’a dla tych projektów nie wyróżnia się niczym szczególnym. Zawiera ona jedynie deklarację zależności między wewnętrznymi i zewnętrznymi bibliotekami.

Co dalej?
Aplikację zbudowaną przy pomocy polecenia mvn clean package (plik ear/target/ear-1.0.ear) umieścić można na serwerze JBoss korzystając z konsoli administratora (http://localhost:8080/admin-console/) lub wtyczki do Maven’a jboss-maven-plugin (http://mojo.codehaus.org/jboss-maven-plugin/).

Podczas tworzenia projektu Maven’owego pod EJB, JPA i JSF wzorowałem się na tutorialach Jacka Laskowskiego i Alexander Shvets:

Mam nadzieję, że mój krótki wpis, udostępniona przykładowa aplikacja oraz ich tutoriale pomogą w tzw. szybkim starcie z Java EE i Maven.

Źródła projektu: JavaEE-app.zip.
Errata: Biblioteki zewnętrzne, EAR i Maven.

Uwaga! Dla przejrzystości artykułu nie omawiałem wszystkich zagadnień wykorzystywanych podczas dewelopmentu aplikacji Java EE (mapowania ORM, zasady działania managed bean’ów w JSF, konfiguracji web.xml, persistence.xml etc.). Zachęcam do ściągnięcia projektu i obejrzenia źródeł.

JBoss Web Services

Po długiej batalii z EJB przyszedł czas na wyjazd do Wenecji. Chwila relaksu i oderwania od klawiatury – smażenie się na plaży w Lido, zwiedzanie wcale nie śmierdzących kanałów miasta, przejażdżka gondolą i niemal bójka w Padwie. Po tych jakże odprężających ćwiczeniach usiadłem na moment w domowym zaciszu przed komputerem i wpadła mi do głowy jedna myśl – jak skorzystać z Web Services w Jboss? Z technologią tą miałem sporo do czynienia w implementacji Oracle’owej na serwerze aplikacyjnym Weblogic. Tam jednak umysł programisty wyręcza myszka i JDeveloper. Generowany przez IDE kod jest nadmiarowy lecz o dziwo zazwyczaj działa :P.

Przystąpmy więc do części bardziej merytorycznej. Zacznę od krótkiego przypomnienia technologii Web Services, standardu SOAP oraz takich terminów jak np. WSDL. Web Service wykorzystuje jako warstwę transportu protokół HTTP (zazwyczaj, inne możliwości: SMTP, FTP, JMS) oraz język XML (w celu strukturyzacji przesyłanych danych). Komunikat SOAP ma formę XML’a składającego się z <Envelope>, oraz zagnieżdżonych w nim nagłówku <Header> i ciele <Body>.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:edu="edu.lantoniak.ws">
  <soapenv:Header/>
  <soapenv:Body>
    <edu:sayHello>Lukasz</edu:sayHello>
  </soapenv:Body>
</soapenv:Envelope>

Za opis udostępnianego przez Nas serwisu odpowiada WSDL. Jest on zwyczajowo umieszczony na serwerze aplikacyjnym i dostępny z poziomu przeglądarki internetowej. Klienci WS, na podstawie informacji zawartych w tym pliku, określić mogą typy wymaganych parametrów poszczególnych metod oraz wartości przez nich zwracanych. Jako deweloper Java rzadko piszę WSDL ręcznie. Deskryptor ów generowany jest automatycznie na podstawie klasy Java (implementacji Web Service’u) opatrzonej odpowiednimi adnotacjami. Poniżej zamieściłem przykładowy WSDL.

<definitions name="HelloService" targetNamespace="edu.lantoniak.ws">
  <types>
    <xs:schema targetNamespace="edu.lantoniak.ws" version="1.0">
      <xs:element name="sayHello" nillable="true" type="xs:string"/>
      <xs:element name="sayHelloResponse" nillable="true" type="xs:string"/>
    </xs:schema>
  </types>
  <message name="HelloWSRemote_sayHelloResponse">
    <part element="tns:sayHelloResponse" name="sayHelloResponse"/>
  </message>
  <message name="HelloWSRemote_sayHello">
    <part element="tns:sayHello" name="sayHello"/>
  </message>
  <portType name="HelloWSRemote">
    <operation name="sayHello" parameterOrder="sayHello">
      <input message="tns:HelloWSRemote_sayHello"/>
      <output message="tns:HelloWSRemote_sayHelloResponse"/>
    </operation>
  </portType>
  <binding name="HelloWSRemoteBinding" type="tns:HelloWSRemote">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="sayHello">
      <soap:operation soapAction=""/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  </binding>
  <service name="HelloService">
    <port binding="tns:HelloWSRemoteBinding" name="HelloWSPort">
      <soap:address location="http://localhost:8080/Tutorial7/HelloWSImpl"/>
    </port>
  </service>
</definitions>

Wewnątrz tagu <types> określa się wszystkie typy danych stosowanych w serwisie (typy parametrów oraz wartości zwracanych wykorzystywanych w metodach). W Naszym przypadku, zdefiniowane są dwa proste typy klasy xs:string: sayHello oraz sayHelloResponse. Tag <message> specyfikuje wszystkie rodzaje wiadomości jakie obsługiwane są przez Web Service. Linijki 14 – 19 wiążą rodzaje wykorzystywanych wiadomości z poszczególnymi operacjami (metodami). Sekcja <binding> wyróżnia poszczególne operacje serwisu oraz definiuje szczegóły dotyczące stosowanego protokołu (w tym przypadku stylu document – por. RPC). Wreszcie, tag <service> specyfikuje nazwę serwisu oraz jego adres.

Implementacja prostego Web Service’u

W Eclipse, podobnie jak w JDeveloper, implementację Web Service’u oraz projektu proxy stosowanego przez klienta, można ponoć (mi się nie udało) w stosunkowo łatwy sposób wyklikać (rodzaje projektu: “Create a Sample Web Service” oraz “Web Service Client”). Zaprezentuję jednak drogą równie przyjemną metodę, nie opierającą się na kreatorach graficznych i klikologii, lecz na nieco bardziej przemyślanym użyciu klawiatury. Web Service tworzyć można na dwa sposoby: bottom up (najpierw klasy Java, a potem WSDL), lub top down (inaczej zwane contract first – najpierw WSDL, a następnie implementacja).

Stwórzmy nowy projekt EJB, a w nim bezstanowe (@Statefull nie jest wspierane, polecam zapoznać się z terminem conversational Web Service) ziarno EJB.

@Remote
@WebService(targetNamespace = "edu.lantoniak.ws")
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT,
             parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface HelloWSRemote {
   public String sayHello(String name);
}

@Stateless
@WebService(serviceName = "HelloService", targetNamespace = "edu.lantoniak.ws",
            name = "HelloWS", endpointInterface = "edu.lantoniak.ws.HelloWSRemote")
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT,
             parameterStyle = SOAPBinding.ParameterStyle.BARE)
public class HelloWSImpl implements HelloWSRemote {
   public String sayHello(String name) {
      return "Hello " + name + "!";
   }
}

Następnie deploy’ujemy aplikację. Uwaga: nazwa pod którą deploy’owana jest aplikacja nie może zawierać spacji! Aby upewnić się, że aplikacja wgrana została pomyślnie, przejdź do http://localhost:8080/jbossws/services i sprawdź, czy na liście serwisów widnieje Twoja nowo dodana usługa. Działanie samego Web Service’u weryfikujemy za pomocą narzędzia SoapUI.

W celu dalszej zabawy, polecam zapoznanie się z podstawowymi adnotacjami @WebMethod, @WebParam, @WebResult, oraz bardziej zaawansowanymi @OneWay i @HandlerChain.

Implementacja klienta standalone

Poniżej znajduje się kod prostego klienta, wywołującego wcześniej stworzony Web Service z poziomu Javy.

public class Main {
   public static void main(String[] args) {
      String endpointURI = "http://localhost:8080/Tutorial7/HelloWSImpl?wsdl";
      try {
         String hello = getPort(endpointURI).sayHello("Lukasz");
         System.out.println(hello);
      } catch (MalformedURLException e) {
         e.printStackTrace();
      }
   }

   private static HelloWSRemote getPort(String endpointURI) throws MalformedURLException {
      QName serviceName = new QName("edu.lantoniak.ws", "HelloService");
      URL wsdlURL = new URL(endpointURI);
      Service service = Service.create(wsdlURL, serviceName);
      return service.getPort(HelloWSRemote.class);
   }
}

Wstrzykiwanie proxy serwisu do ziaren EJB

W celu wywołania serwisu w ziarnie EJB, skorzystać należy z narzędzia wsconsume wchodzącego w skład serwera aplikacyjnego JBoss. Narzędzie to generuje klasy proxy Web Service’u, które następnie wepniemy za pomocą adnotacji @WebServiceRef. Stwórzmy nowy, pusty katalog w dowolnym miejscu na dysku twardym i wywołajmy polecenie: $JBOSS_HOME\bin\wsconsume.bat -p edu.lantoniak.ws.proxy http://localhost:8080/Tutorial7/HelloWSImpl?wsdl. Wygenerowane klasy (wraz z katalogami pakietu – w tym przypadku edu.lantoniak.ws.proxy) kopiujemy np. do katalogu generated w projekcie modułu EJB. Następnie w Eclipse klikamy prawym przyciskiem na projekt -> Properties -> Java Build Path -> Libraries -> Add Class Folder i wskazujemy na katalog generated. Pozostało Nam jedynie stworzenie nowego ziarna EJB korzystającego z Web Service’u:

@Stateless
public class StatelessSampleEJB implements StatelessSampleEJBRemote {
   @WebServiceRef(wsdlLocation = "http://localhost:8080/Tutorial7/HelloWSImpl?wsdl")
   private HelloService helloService;

   public String sayHello(String name) {
      return helloService.getHelloWSPort().sayHello(name);
   }
}

Źródła projektów: Tutorial 7 All.zip.

EJB Security

Tym razem poruszę zagadnienia związane z zabezpieczeniem bean’ów działających w ramach kontenera JBoss AS EJB. W tym celu stworzymy arcyciekawą aplikację – komercyjny kalkulator. Osoby niezarejestrowane będą miały możliwość wykonywania jedynie operacji dodawania, podczas gdy każdy użytkownik należący do grupy ENTERPRISE także operację mnożenia. Serwer aplikacyjny JBoss posiada wbudowaną obsługę standardu LDAP, relacyjnych baz danych oraz plików płaskich, gdzie składować możemy nazwy użytkowników, ich hasła oraz informacje na temat przynależności do poszczególnych ról. Dla zachowania prostoty owego przykładu zdecydowałem się na przechowywanie owych informacji w plikach płaskich.

Pierwszy krok, jaki należy wykonać, to konfiguracja serwera aplikacyjnego, czyli stworzenie tzw. securoty domain, przykładowego zarejestrowanego użytkownika, przypisanie mu hasła oraz roli ENTERPRISE. Na początku (czyt. wewnątrz taga <policy>) pliku $JBOSS_HOME/server/default/conf/login-config.xml dopisujemy:

<application-policy name="CommercialCalculator">
  <authentication>
    <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule"
                  flag = "required">
      <module-option name="unauthenticatedIdentity">anonymous</module-option>
      <module-option name="usersProperties">props/cc-users.properties</module-option>
      <module-option name="rolesProperties">props/cc-roles.properties</module-option>
    </login-module>
  </authentication>
</application-policy>

Następnie tworzymy dwa pliki cc-users.properties oraz cc-roles.properties (w podkatalogu props/). Struktura plików powinna mieć formę properties, tzn. każdy wiersz posiada parę klucz-wartość rozdzieloną znakiem równości. Linia rozpoczęta symbolem # traktowana jest jako komentarz. Plik cc-users.properties zawiera nazwy użytkowników oraz przypisane im hasła (niezaszyfrowane niestety, aczkolwiek istnieje taka możliwość: http://community.jboss.org/wiki/UsersRolesLoginModule). Drugi plik zaś, definiuje role posiadane przez danego użytkownika. W przypadku większej niż jedna ilości ról, należy rozdzielić je przecinkiem. Zwróćmy uwagę na użytkownika anonimowego, gdyż musi on posiadać odpowiedni wpis w pliku login-config.xml, jak i cc-users.properties. Przykładowe pliki konfiguracyjne:

cc-users.properties:

foo=bar
anonymous=

cc-roles.properties:

foo=ENTERPRISE

Zastanawia mnie teraz, czy istnieje możliwość definiowania hierarchii ról… Wracając do przykładu – stwórzmy ziarno EJB (Stateless, Remote) kalkulatora wykorzystując adnotacje @RolesAllowed oraz @PermitAll. W tutorialu tym nie opisuję szczegółowo kolejnych kroków tworzenia projektu EJB w Eclipse IDE. Jeśli ktoś przypadkiem trafił na ten mini-artykuł z wyszukiwarki Google, odsyłam do mojego poprzedniego wpisu na temat EJB i JPA, gdzie wymagane kroki zostały szczegółowo przedstawione.

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;

import org.jboss.ejb3.annotation.SecurityDomain;

@Stateless
@SecurityDomain("CommercialCalculator")
@RolesAllowed("ENTERPRISE")
public class CommercialCalculator implements CommercialCalculatorRemote {
    @PermitAll
    public int add(int a, int b) {
        return a + b;
    }

    public int multiply(int a, int b) {
        return a * b;
    }
}

Klasa CommercialCalculator opatrzona została adnotacją @SecurityDomain, która specyfikuje, z której domeny bezpieczeństwa korzysta ziarno EJB. Domena ta zdefiniowana została przez Nas uprzednio w pliku login-config.xml. Adnotację @RolesAllowed stosować można w odniesieniu do całej klasy (definiuje wtedy role wymagane domyślnie do użycia jakiejkolwiek z metod) lub dla pojedynczej metody. @PermitAll przesłania adnotację @RolesAllowed udostępniając wszystkim wywołanie owej metody.

Przedstawię teraz kod klienta. Niestety JBoss nie trzyma się tutaj standardów. Serwer aplikacyjny nie korzysta z atrybutów Context.SECURITY_PRINCIPAL oraz Context.SECURITY_CREDENTIALS. Mało tego – udostępnia on całkowicie odrębny mechanizm logowania! Szczegóły widać gołym okiem na poniższym listingu (por.: http://nurkiewicz.blogspot.com/2009/08/invoking-secured-remote-ejb-in-jboss-5.html i http://community.jboss.org/wiki/SecurityFAQ).

import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import org.jboss.security.client.SecurityClient;
import org.jboss.security.client.SecurityClientFactory;
import edu.lantoniak.ejb.security.CommercialCalculatorRemote;

public class Main {
    public static void main(String[] args) throws Exception {
        SecurityClient securityClient = null;
        try {
            securityClient = SecurityClientFactory.getSecurityClient();
//          securityClient.setSimple("anonymous", "");
            securityClient.setSimple("foo", "bar");
            securityClient.login();

            Properties props = new Properties();
            props.put(Context.INITIAL_CONTEXT_FACTORY,
                      "org.jnp.interfaces.NamingContextFactory");
            /* Not supported by JBoss AS. */
//          props.put(Context.INITIAL_CONTEXT_FACTORY,
//                    "org.jboss.security.jndi.JndiLoginInitialContextFactory");
//          props.put(Context.SECURITY_PRINCIPAL, "foo");
//          props.put(Context.SECURITY_CREDENTIALS, "bar");
            props.put(Context.PROVIDER_URL, "localhost:1099");
            props.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");

            Context ctx = new InitialContext(props);

            CommercialCalculatorRemote calc =
                (CommercialCalculatorRemote) ctx.lookup("CommercialCalculator/remote");
            System.out.println(calc.multiply(2, 2));
            System.out.println(calc.add(1, 2));
        } finally {
            if (securityClient != null) {
                securityClient.logout();
            }
        }
    }
}

Zachęcam czytelników do zabawy w komentowanie i odkomentowywanie na przemian linii 13, 14 oraz 32, 33.

Źródła projektów: Tutorial 6 All.zip.

EJB i JPA

Witam!

Na wstępie zaznaczę, iż jest to mój pierwszy wpis typu “tutorial” na internetowym blogu. Proszę o wszelką wyrozumiałość oraz krytyczne komentarze w celu podniesieniu poziomu kolejnych:). Po tej krótkiej, ciekawej i jakże interesującej przedmowie przejdźmy do meritum sprawy, a mianowicie EJB i JPA. Mateusz Mrozowski na swoim blogu (http://tech.mrozewski.pl) opublikował szereg ćwiczeń związanych z EJB dla początkujących. Początkowo korzystał on z Elcipse i JBoss AS. Część poświęconą JPA przeniósł jednak (dla urozmaicenia:P) na NetBeans i Glassfish. Moja wewnętrzna niechęć do owych środowisk okazała się wystarczającym impulsem do powstania tego mini-artykułu. A więc do dzieła!

Naszym celem jest stworzenie trzech projektów:

  1. projektu JPA zawierającego mapowanie tabel relacyjnej bazy danych na encje.
  2. projektu EJB udostępniającego podstawowe operacje na danych. Coś na wzór Data Access Objects.
  3. projektu aplikacji klienckiej.

Jako docelową bazą danych posłużę się Oracle 10g XE (10.2). Dlaczego?! Ci co mnie znają pewnie wiedzą, i niech tak pozostanie. Oficjalnie zachęcam wszystkich do korzystania z baz Oracle, gdyż nie są trudne w instalacji, jeszcze darmowe (!) oraz szeroko stosowane w projektach komercyjnych. W projekcie JPA zmapuję tylko fragment jednej tabelki przykładowego schematu HR dalszą zabawę z JPA pozostawiając czytelnikowi.

Projekt JPA

Z Eclipse’owego menu wybieramy File -> New -> JPA Project, wpisujemy nazwę Tutorial 3 i klikamy Finish. Następnie tworzymy klasę encji edu.lantoniak.ejb3.jpa.Job oraz wklejamy kod znajdujący się na listingu zamieszczonym poniżej.

@Entity
@Table(name="JOBS")
public class Job implements Serializable {
    @Id
    @Column(name="JOB_ID")
    private String jobId;

    @Column(name="JOB_TITLE")
    private String jobTitle;

    /* Getters & setters */
    ...
}

Pamiętać należy o przeciążeniu metod equals() oraz hashCode() (zgodnie z kontraktem znanym z przygotowań do SCJP). Prezentowany przykład mapowania jest dziecinnie prosty i nikomu znającemu język angielski nie trzeba go zapewne objaśniać.

Ostatni krok niezbędny do ukończenia projektu JPA, polega na edycji pliku persistence.xml i wyspecyfikowania źródła danych, z którego korzysta aplikacja. Plik ten powinien mieć postać podobną do tej z drugiego listingu.

<?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="Tutorial 3">
        <jta-data-source>java:/OracleDS</jta-data-source>
    </persistence-unit>
</persistence>

Dzięki tagowi <jta-data-source> deskryptor połączenia do bazy danych pobrany zostanie z zasobów serwera aplikacyjnego. Domyślna konfiguracja JBoss nie posiada zainstalowanego sterownika JDBC Oracle (link). Plik ojdbc14.jar umieścić należy w katalogu $JBOSS_HOME/server/default/lib/. Następnie w katalogu $JBOSS_HOME/server/default/deploy/ tworzymy plik oracle-ds.xml, którego przykładową zawartość prezentuje kolejny listing. Plik ten definiuje między innymi connection string, adres bazy danych, użytkownika, hasło oraz wielkość dostępnej puli połączeń.

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
    <local-tx-datasource>
        <jndi-name>OracleDS</jndi-name>
        <connection-url>jdbc:oracle:thin:@localhost:1521:XE</connection-url>
        <driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
        <user-name>hr</user-name>
        <password>hr</password>
        <min-pool-size>1</min-pool-size>
        <max-pool-size>5</max-pool-size>
        <exception-sorter-class-name>
            org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter
        </exception-sorter-class-name>
        <metadata>
            <type-mapping>Oracle9i</type-mapping>
        </metadata>
    </local-tx-datasource>
</datasources>

Otwieramy w przeglądarce internetowej konsolę administracyjną JBoss – http://localhost:8080/admin-console/ (domyślnie admin/admin). Uwaga: ważne, aby najpierw uruchomić serwer aplikacyjny, a dopiero potem bazę danych Oracle. Występuje konflikt domyślnych portów i konsola administracyjna nie zbinduje się na port 8080. Tak to działa przynajmniej u mnie.. Nie wierzysz – sprawdź, a jeśli się mylę umieść komentarz. Tak czy owak, po zalogowaniu się, powinniśmy widzieć w sekcji Datasources źródło danych OracleDS ze statusem up.

Projekt EJB

Przypomnę, iż projekt ten pełni funkcję warstwy DAO – udostępnia operacje wyszukiwania oraz DML wyższym warstwom aplikacji. Tworzymy nowy projekt EJB o nazwie Tutorial 3 EJB. We właściwościach projektu dodajemy mozolnie skonfigurowany uprzednio moduł JPA (Tutorial 3) do Java Build Path. Następnie tworzymy nowe bezstanowe ziarno EJB (patrz listing 4).

@Stateless
public class JobDAO implements JobDAORemote, JobDAOLocal {
    @PersistenceContext
    private EntityManager em;

    public JobDAO() {
    }

    @Interceptors(DAOInterceptor.class)
    public void add(Job job) {
        em.persist(job);
    }
}

Do bean’a JobDAO, za pomocą adnotacji @PersistenceContext, wstrzykujemy EntityManager. Obiekt ten umożliwia podstawowe operacje na encjach. Jeśli chodzi o szerszy opis klasy PersistenceContext, to odsyłam do fachowej literatury, z której dowiesz się znacznie więcej niż z tego prostego tutorialu. W prezentowanym powyżej kodzie, niespodziankę stanowi interceptor DAOInterceptor. Interceptor jest to klasa umożliwiająca wykonanie pewnych operacji przed oraz po wykonaniu danej metody (w tym przypadku add()), lub wszystkich metod danej klasy. Kod interceptora przedstawia się następująco:

public class DAOInterceptor {
    @AroundInvoke
    public Object profile(InvocationContext invocation) throws Exception {
        if ("add".equals(invocation.getMethod().getName())) {
            if (invocation.getParameters()[0] instanceof Job) {
                Job job = (Job) invocation.getParameters()[0];
                job.setJobTitle(job.getJobTitle() + " ADDED");
            }
        }
        return invocation.proceed();
    }
}

Obiekt InvocationContext enkapsuluje wszelkie dobrodziejstwa i niebezpieczeństwa jakie niesie za sobą mechanizm refleksji. W tym przypadku do każdego nowo tworzonego tytułu stanowiska, doklejany sufiks ” ADDED”. Oczywiście, zaprezentowany interceptor nie ma żadnego sensu biznesowego. Bardziej użyteczny przykład wykorzystania interceptora dotyczy wypełniania kolumn audytowych i opisany został w artykule “Using a Hibernate Interceptor To Set Audit Trail Properties”.

Projekt aplikacji klienckiej
Prosty projekt umożliwiający dostęp do wcześniej powstałego obiektu DAO tworzymy przez opcje File -> New -> Project -> Application Client Project. Mateusz Mrozowski opisuje na swoim blogu procedurę dostępu do ziaren EJB spoza kontenera. Dla porządku umieszczam jednak kod aplikacji klienckiej:

public class Main {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
        props.put(Context.PROVIDER_URL, "localhost:1099");
        props.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
        try {
            Context ctx = new InitialContext(props);
            JobDAORemote jobDAO = (JobDAORemote) ctx.lookup("JobDAO/remote");
            Job job = new Job();
            job.setJobId("LUK_DYR");
            job.setJobTitle("Dyrektor");
            jobDAO.add(job);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

Źródła projektów: Tutorial 3 All.zip.