Enterprise Java Development@TOPIC@
Although the instructions thus far have provided details at the filesystem and Maven level, you have surely started importing and developing the projects within your IDE by now. The one gotcha remaining to solve are those pesky JNDI names and keeping our Java code ignorant of version numbers. Version numbers have a place but they rarely have a place in lookups when the version# can be updated with even trivial releases.
basicejb-ear-1.0-SNAPSHOT/basicejb-ejb-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
We initially solved the problem by getting the pom involved with expanding the version# and passing the result to the IT test as a system property. That worked well within the Maven build. However, we want to develop and run our IT tests outside of Maven once we have leveraged enough of Maven to get started.
<jndi.name.reservation>
ejb:basicejb-ear-${project.version}/basicejb-ejb-${project.version}/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
</jndi.name.reservation>
private static final String reservationJNDI = System.getProperty("jndi.name.reservation");
In this section we are going to add configuration options that will eliminate the version#s from the JNDI names
basicejb-ear/basicejb-ejb/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
This will permit our IT tests to form a reasonable default. We can still override this default with a system property.
private static final String reservationJNDI = System.getProperty("jndi.name.reservation",
"ejb:basicejb-ear/basicejb-ejb/ReservationEJB!"+ReservationRemote.class.getName());
Build the RMI Test module and notice the application/EAR portion of the JNDI name printed to the server's console and server.log contains the version# of the EAR.
$ mvn verify -f basicejb-test
java:jboss/exported/basicejb-ear-1.0-SNAPSHOT/basicejb-ejb-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
Add the following declaration of the EAR plugin and add a configuration option to change the applicationName to be the artifactId for the module.
<build>
<plugins>
<!-- provide properties here to impact the EAR packaging -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ear-plugin</artifactId>
<configuration>
<!-- eliminates use of version in EAR JNDI name portion -->
<applicationName>${project.artifactId}</applicationName>
</configuration>
</plugin>
</plugins>
</build>
Notice that with the lack of a maven-ear-plugin specification, the EAR plugin defaults to generate an application.xml according to a J2EE 1.3 DTD.
$ cat basicejb-ear/target/application.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC
"-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN"
"http://java.sun.com/dtd/application_1_3.dtd">
<application>
<display-name>basicejb-ear</display-name>
<description>This project provides a sample EAR for the Java EE components
associated with the overall project.</description>
<module>
<ejb>basicejb-ejb-1.0-SNAPSHOT.jar</ejb>
</module>
</application>
Since this is our first reference to the EAR plugin, add a pluginManagement definition of this plugin in the parent pom.xml.
<properties>
...
<maven-ear-plugin.version>3.0.1</maven-ear-plugin.version>
...
<build>
<pluginManagement>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ear-plugin</artifactId>
<version>${maven-ear-plugin.version}</version>
<configuration>
<version>7</version>
</configuration>
</plugin>
Once we add the plugin definition to use JavaEE 7, the EAR plugin generates an application.xml according to a JavaEE 7 schema. There is no noticeable difference other than the change in schema versus DTD reference.
$ cat basicejb-ear/target/application.xml
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/application_7.xsd" version="7">
<application-name>basicejb-ear</application-name>
<description>This project provides a sample EAR for the Java EE components
associated with the overall project.</description>
<display-name>basicejb-ear</display-name>
<module>
<ejb>basicejb-ejb-1.0-SNAPSHOT.jar</ejb>
</module>
</application>
Rebuild and redeploy the EAR and note the application portion of the JNDI name is now a fixed value. Since our IT test still references the older name it fails.
java:jboss/exported/basicejb-ear/basicejb-ejb-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
$ mvn clean install -rf basicejb-ear ... 18:09:46,132 INFO (ReservationIT.java:37) -*** testPing *** [ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.894 s <<< FAILURE! - in org.myorg.basicejb.earejb.ReservationIT [ERROR] testPing(org.myorg.basicejb.earejb.ReservationIT) Time elapsed: 0.767 s <<< ERROR! org.jboss.ejb.client.RequestSendFailedException: EJBCLIENT000409: No more destinations are available at org.myorg.basicejb.earejb.ReservationIT.testPing(ReservationIT.java:39) ... [INFO] Reactor Summary: [INFO] [INFO] Basic EJB Exercise::EAR ............................ SUCCESS [ 1.533 s] [INFO] Basic EJB Exercise::Remote Test .................... FAILURE [ 5.656 s] [INFO] Basic EJB Exercise::WAR ............................ SKIPPED [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE
Update the RMI Test/pom.xml to eliminate the version# of the application and re-run the prior build. We should be working again but still with a version# in the EJB.
<jndi.name.reservation>ejb:basicejb-ear/basicejb-ejb-${project.version}/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote</jndi.name.reservation>
$ mvn clean install -rf basicejb-ear ... [INFO] Reactor Summary: [INFO] [INFO] Basic EJB Exercise::EAR ............................ SUCCESS [ 1.573 s] [INFO] Basic EJB Exercise::Remote Test .................... SUCCESS [ 5.851 s] [INFO] Basic EJB Exercise::WAR ............................ SUCCESS [ 3.068 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS
Add a modules section to the EAR plugin declaration. The default for this relied on the dependencies and this caused the EJB.jar to be hosted in the EAR with its fully qualified version. Here we supply a specific name we want for the EJB.jar. This will get reflected in the JNDI name.
I also ask that you add a defaultLibBundleDir specification the to descriptor at this time. This will define a directory within the EAR (similar to WEB-INF/lib) whose contents will be added to the global classpath of components loaded by the EAR. We don't have a use for that yet, but this is the last tweak we will be making to EARs for a while.
<build>
<plugins>
<!-- provide properties here to impact the EAR packaging -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ear-plugin</artifactId>
<configuration>
<!-- names directory within EAR to place jars to be in classpath -->
<defaultLibBundleDir>lib</defaultLibBundleDir>
<!-- eliminates use of version in EAR JNDI name portion -->
<applicationName>${project.artifactId}</applicationName>
<modules>
<!-- eliminates use of the version in the EJB JNDI name -->
<ejbModule>
<groupId>${project.groupId}</groupId>
<artifactId>basicejb-ejb</artifactId>
<bundleFileName>basicejb-ejb.jar</bundleFileName>
</ejbModule>
</modules>
</configuration>
</plugin>
</plugins>
</build>
Rebuild and redeploy the EAR and note the module portion of the JNDI name is now a fixed value. Since our IT test still references the older name it fails.
java:jboss/exported/basicejb-ear/basicejb-ejb/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
$ mvn clean install -rf basicejb-ear ... java.lang.IllegalStateException: EJBCLIENT000025: No EJB receiver available for handling [appName:basicejb-ear, moduleName:basicejb-ejb-1.0-SNAPSHOT, distinctName:] combination for invocation context org.jboss.ejb.client.EJBClientInvocationContext@3d01e5eb at org.jboss.ejb.client.EJBClientContext.requireEJBReceiver(EJBClientContext.java:749) ... at org.myorg.basicejb.earejb.ReservationIT.testPing(ReservationIT.java:37) ... [INFO] Reactor Summary: [INFO] [INFO] Basic EJB Exercise::EAR ............................ SUCCESS [ 1.369 s] [INFO] Basic EJB Exercise::Remote Test .................... FAILURE [ 5.643 s] [INFO] Basic EJB Exercise::WAR ............................ SKIPPED [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE
Update the RMI Test/pom.xml to eliminate the version# of the module and re-run the prior build. We should be working again but still with a version# in the EJB.
<jndi.name.reservation>ejb:basicejb-ear/basicejb-ejb/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote</jndi.name.reservation>
$ mvn clean install -rf basicejb-ear ... [INFO] Reactor Summary: [INFO] [INFO] Basic EJB Exercise::EAR ............................ SUCCESS [ 1.424 s] [INFO] Basic EJB Exercise::Remote Test .................... SUCCESS [ 5.811 s] [INFO] Basic EJB Exercise::WAR ............................ SUCCESS [ 3.000 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS
Update your IT test to assign a reasonable default for the JNDI name. To test the change, comment out the system property specification in the failsafe configuration.
public class ReservationIT {
private static final String reservationJNDI = System.getProperty("jndi.name.reservation",
"ejb:basicejb-ear/basicejb-ejb/ReservationEJB!"+ReservationRemote.class.getName());
<systemPropertyVariables>
<!--
<jndi.name.reservation>ejb:basicejb-ear/basicejb-ejb/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote</jndi.name.reservation>
-->
</systemPropertyVariables>
$ mvn clean install -rf basicejb-ear ... [INFO] Reactor Summary: [INFO] [INFO] Basic EJB Exercise::EAR ............................ SUCCESS [ 1.522 s] [INFO] Basic EJB Exercise::Remote Test .................... SUCCESS [ 5.810 s] [INFO] Basic EJB Exercise::WAR ............................ SUCCESS [ 3.119 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS
If you are not sure if this is using your default JNDI name you can optionally munge the value in the IT test and note it will then fail.
You may optionally re-enable the system property for the JNDI name but I would suggest against it until there is a need to derive the name a different way. It can get confusing when a default gets derived from multiple locations.
Note the JNDI name of the WAR when it was deployed above. We want to remove the version# from the deployed WAR.
java:jboss/exported/basicejb-war-1.0-SNAPSHOT/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote
If we were deploying the WAR within an EAR, we would have added the following to the EAR plugin.
<webModule>
<groupId>${project.groupId}</groupId>
<artifactId>basicejb-war</artifactId>
<contextRoot>basicejb-war</contextRoot>
</webModule>
Since we deploy a naked WAR, we cannot use the standard EAR technique. However, we can accomplish our goal by adding a jboss-specific deployment descriptor (jboss-web.xml) to the WAR (WEB-INF directory).
$ mkdir basicejb-war/src/main/webapp/WEB-INF/
$ cat basicejb-war/src/main/webapp/WEB-INF/jboss-web.xml
<jboss-web>
<!-- needed to always assure that version# does not get into JNDI name -->
<context-root>basicejb-war</context-root>
</jboss-web>
The source directory for the WEB-INF and WAR content data is in src/main/webapp. Artifacts in src/main/resources will end up in WEB-INF/classes.
Rebuild and redeploy the WAR and note the module portion of the JNDI name is now a fixed value. Since our IT test still references the older name it fails.
java:jboss/exported/basicejb-war/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote
$ mvn clean install -rf basicejb-war ... testPing(org.myorg.basicejb.warejb.ReservationIT) Time elapsed: 0.766 sec <<< ERROR! java.lang.IllegalStateException: EJBCLIENT000025: No EJB receiver available for handling [appName:, moduleName:basicejb-war-1.0-SNAPSHOT, distinctName:] combination for invocation context org.jboss.ejb.client.EJBClientInvocationContext@1fafcae2 at org.jboss.ejb.client.EJBClientContext.requireEJBReceiver(EJBClientContext.java:749) ... at com.sun.proxy.$Proxy5.ping(Unknown Source) at org.myorg.basicejb.warejb.ReservationIT.testPing(ReservationIT.java:37) testPing(org.myorg.basicejb.warejb.ShopperIT) Time elapsed: 0 sec <<< ERROR! javax.naming.NamingException: Failed to create proxy at org.jboss.ejb.client.EJBClientContext.requireEJBReceiver(EJBClientContext.java:813) ... at javax.naming.InitialContext.lookup(InitialContext.java:411) at org.myorg.basicejb.warejb.ShopperIT.testPing(ShopperIT.java:32) ... Tests in error: ReservationIT.testPing:37 » IllegalState EJBCLIENT000025: No EJB receiver avai... ShopperIT.testPing:32 » Naming Failed to create proxy ... [INFO] BUILD FAILURE
I thought it was interesting that the IT test for the stateless EJB does not fail until the call is actually made to ping(). The IT test for the stateful EJB fails during the JNDI lookup().
Update the WAR/pom.xml to eliminate the version# of the module for both JNDI names and re-run the prior build. We should be working again but still with a version# in the WAR.
<jndi.name.reservation>ejb:/basicejb-war/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote</jndi.name.reservation>
<jndi.name.shopper>ejb:/basicejb-war/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote?stateful</jndi.name.shopper>
$ mvn clean install -rf basicejb-war ... [INFO] BUILD SUCCESS
Be sure to append "?stateful" to your stateful EJB JNDI name. We need this for "ejb:" naming contexts for EJB Client to work correctly. there should be no issue promoting those details to the client since a client will be designed differently when working with a stateless or stateful EJB.
Update your IT tests to assign a reasonable default for the JNDI name. To test the change, comment out the system property specification in the failsafe configuration.
public class ReservationIT {
private static final String reservationJNDI = System.getProperty("jndi.name.reservation",
"ejb:/basicejb-war/ReservationEJB!"+ReservationRemote.class.getName());
public class ShopperIT {
private static final String shopperJNDI = System.getProperty("jndi.name.shopper",
"ejb:/basicejb-war/ShopperEJB!"+ShopperRemote.class.getName()+"?stateful");
<systemPropertyVariables>
<!--
<jndi.name.reservation>ejb:/basicejb-war/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote</jndi.name.reservation>
<jndi.name.shopper>ejb:/basicejb-war/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote?stateful</jndi.name.shopper>
-->
</systemPropertyVariables>
$ mvn clean install -rf basicejb-ear ... [INFO] BUILD SUCCESS
If you are not sure if this is using your default JNDI name you can optionally munge the value in the IT test and note it will then fail.
You may optionally re-enable the system property for the JNDI names but I would suggest against it until there is a need to derive the name a different way. It can get confusing when a default gets derived from multiple locations. If we end up with several IT tests forming the same JNDI name, I would suggest moving the construction of the JNDI name to a test utility class.
EAR-based names
impacted using the META-INF/application.xml
application.xml can be auto-built and configured using EAR plugin
EAR name configured with application-name element of application.xml
EJB name configured with the name of the EJB module within the EAR
EJB module can be renamed using ejbModule.bundleFileName of EAR plugin
WAR name configured with module.web.context-root element of application.xml
WAR-based names
no JavaEE/WAR standard for naming a naked-deployed WAR
could deploy WAR within WAR to help control JNDI name
JBoss uses the context-root supplied in WEB-INF/jboss-web.xml
Reasonable defaults in IT classes
Makes them IDE-friendly
Reduces dependency on full Maven build lifecycle for everything you do
Can be developed, run, and re-factored more quickly
Figure 51.1. Sample META-INF/application.xml
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/application_7.xsd" version="7">
<application-name>basicejb-ear</application-name>
<description>This project provides a sample EAR for the Java EE components
associated with the overall project.</description>
<display-name>basicejb-ear</display-name>
<module>
<web>
<web-uri>(example web module).war</web-uri>
<context-root>(example-fixed-web-module-name)</context-root>
</web>
</module>
<module>
<ejb>basicejb-ejb.jar</ejb>
</module>
<library-directory>lib</library-directory>
</application>
Figure 51.2. Sample EAR plugin configuration
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ear-plugin</artifactId>
<configuration>
<!-- names directory within EAR to place jars to be in classpath -->
<defaultLibBundleDir>lib</defaultLibBundleDir>
<!-- eliminates use of version in EAR JNDI name portion -->
<applicationName>${project.artifactId}</applicationName>
<modules>
<webModule>
<groupId>${project.groupId}</groupId>
<artifactId>(example-web-module-artifactId)</artifactId>
<contextRoot>(example-fixed-web-module-name)</contextRoot>
</webModule>
<!-- eliminates use of the version in the EJB JNDI name -->
<ejbModule>
<groupId>${project.groupId}</groupId>
<artifactId>basicejb-ejb</artifactId>
<bundleFileName>basicejb-ejb.jar</bundleFileName>
</ejbModule>
</modules>
</configuration>
</plugin>
Figure 51.3. Sample WEB-INF/jboss-web.xml
<jboss-web>
<!-- needed to always assure that version# does not get into JNDI name -->
<context-root>basicejb-war</context-root>
</jboss-web>