Enterprise Java Development@TOPIC@

Chapter 49. WAR Deployment

49.1. Purpose
49.1.1. Goals
49.1.2. Objectives
49.2. Create WAR Module
49.3. Add RMI Test
49.4. Embed EJB in WAR Module
49.5. Summary

WARs are typically used to deploy web-tier components and this WAR may do that at some point. However, at this point in time we would like to take advantage of the WAR as a deployment artifact for EJBs. Starting with JavaEE 6, EJBs can be flexibly deployed embedded within the WAR or similar to an EAR by hosting EJB archives using dependencies.

  1. Create the sub-project directory for the WAR.

    $ mkdir basicejb-war
    
  2. Add the initial entries for the WAR pom.xml.

    
    <?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">
        <parent>
            <groupId>myorg.basicejb</groupId>
            <artifactId>basicejbEx</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>

        <artifactId>basicejb-war</artifactId>
        <packaging>war</packaging>

        <name>Basic EJB Exercise::WAR</name>
        <description>
            This project provides a sample WAR for the Java EE components
            associated with the overall project.
        </description>

        <dependencies>
        </dependencies>

        <build>
        </build>
    </project>
  3. Add the EJB dependency to the WAR. Use exclusions to keep any unwanted 3rd party .jars from being brought along.

    
        <dependencies>
            <dependency>
                <groupId>${project.groupId}</groupId>
                <artifactId>basicejb-ejb</artifactId>
                <version>${project.version}</version>
                <type>ejb</type>
                <exclusions>
                    <!-- server doesn't want to see already provided jars -->
                    <exclusion>
                        <groupId>org.slf4j</groupId>
                        <artifactId>slf4j-api</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
  4. Attempt to build the WAR. It should fail because we have not yet added a WEB-INF/web.xml or have not configured the plugin to ignore it.

    $ mvn clean package
    ...
    [ERROR] Failed to execute goal org.apache.maven.plugins:maven-war-plugin:2.2:war (default-war) on project basicejb-war: Error assembling WAR: webxml attribute is required (or
    pre-existing WEB-INF/web.xml if executing in update mode)
    
  5. Add the following property and pluginManagement to your root pom.xml. The plugin definition allows our WAR to be deployed without a WEB-INF/web.xml deployment descriptor. The version is required once we explicitly mention the plugin.

    
        <properties>
            ...
            <maven-war-plugin.version>3.2.2</maven-war-plugin.version>
    
        <build>
            <pluginManagement>
                <plugins>
                    ...
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-war-plugin</artifactId>
                        <version>${maven-war-plugin.version}</version>
                        <configuration>
                           <failOnMissingWebXml>false</failOnMissingWebXml>
                        </configuration>
                    </plugin>
  6. Verify the WAR builds.

    $ mvn clean package
    ...
    ...
    [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ basicejb-war ---
    ...
    [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ basicejb-war ---
    ...
    [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ basicejb-war ---
    ...
    [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ basicejb-war ---
    ...
    [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ basicejb-war ---
    ...
    [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ basicejb-war ---
    ...
    [INFO] --- maven-war-plugin:3.2.2:war (default-war) @ basicejb-war ---
    [INFO] Packaging webapp
    [INFO] Assembling webapp [basicejb-war] in [/home/jcstaff/proj/basicejbEx/basicejb-war/target/basicejb-war-1.0-SNAPSHOT]
    [INFO] Processing war project
    [INFO] Webapp assembled in [34 msecs]
    [INFO] Building war: /home/jcstaff/proj/basicejbEx/basicejb-war/target/basicejb-war-1.0-SNAPSHOT.war
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    
  7. Inspect the generated WAR archive. Notice how the EJB we developed in the previous chapter and included as a dependency here was brought into the archive.

    $ jar tf target/basicejb-war-1.0-SNAPSHOT.war
    ...
    WEB-INF/classes/
    WEB-INF/lib/basicejb-ejb-1.0-SNAPSHOT.jar
    ...
    
  8. Add a cargo-maven-plugin declaration to the WAR module to deploy the WAR. Since we are deploying the local artifact and a WAR is a deployable -- we do not need to specify this artifact as a deployable.

    
    ...
        <build>
            <plugins>
                ...
                <!-- artifacts to deploy to server. this module by default -->
                <plugin>
                    <groupId>org.codehaus.cargo</groupId>
                    <artifactId>cargo-maven2-plugin</artifactId>
                </plugin>
  9. Verify the WAR module builds, deploys to the server, and undeploys from the server as part of the build lifecycle.

    
    $ mvn clean verify
    ...
    [INFO] --- maven-war-plugin:3.2.2:war (default-war) @ basicejb-war ---
    [INFO] Packaging webapp
    [INFO] Assembling webapp [basicejb-war] in [/home/jcstaff/proj/basicejbEx/basicejb-war/target/basicejb-war-1.0-SNAPSHOT]
    [INFO] Processing war project
    [INFO] Webapp assembled in [56 msecs]
    [INFO] Building war: /home/jcstaff/proj/basicejbEx/basicejb-war/target/basicejb-war-1.0-SNAPSHOT.war
    [INFO] 
    [INFO] --- cargo-maven2-plugin:1.4.3:redeploy (cargo-prep) @ basicejb-war ---
    Oct 11, 2014 2:11:09 AM org.xnio.Xnio <clinit>
    INFO: XNIO version 3.2.2.Final
    Oct 11, 2014 2:11:09 AM org.xnio.nio.NioXnio <clinit>
    INFO: XNIO NIO Implementation Version 3.2.2.Final
    Oct 11, 2014 2:11:09 AM org.jboss.remoting3.EndpointImpl <clinit>
    INFO: JBoss Remoting version 4.0.3.Final
    ...
    [INFO] --- cargo-maven2-plugin:1.4.3:undeploy (cargo-post) @ basicejb-war ---
    ...
    [INFO] BUILD SUCCESS
  10. Note the JNDI names printed in the server console and server.log.

    02:15:34,284 INFO  [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-4) 
    JNDI bindings for session bean named ReservationEJB in deployment unit deployment "basicejb-war-1.0-SNAPSHOT.war" are as follows:
    
            java:global/basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationLocal
            java:app/basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationLocal
            java:module/ReservationEJB!org.myorg.basicejb.ejb.ReservationLocal
            java:global/basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
            java:app/basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
            java:module/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
            java:jboss/exported/basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
    
    02:15:34,309 INFO  [org.jboss.weld.deployer] (MSC service thread 1-4) JBAS016005: Starting Services for CDI deployment: basicejb-war-1.0-SNAPSHOT.war
    02:15:34,317 INFO  [org.jboss.weld.deployer] (MSC service thread 1-1) JBAS016008: Starting weld service for deployment basicejb-war-1.0-SNAPSHOT.war
    02:15:34,624 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-1) JBAS017534: Registered web context: /basicejb-war-1.0-SNAPSHOT
    02:15:34,636 INFO  [org.jboss.as.server] (management-handler-thread - 1) JBAS018559: Deployed "basicejb-war-1.0-SNAPSHOT.war" (runtime-name : "basicejb-war-1.0-SNAPSHOT.war")
    

    The JNDI names starting with java:jboss/exported are especially important because they are available to remote clients.

    java:jboss/exported/basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
    

    The following will be the base JNDI name of the EJB deployed by the WAR.

    /basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
    

    Compare that to the base name used by the EJB deployed by the EAR. Notice that we have no application name in the WAR-deployed EJB and the module is named after the hosting WAR and and not the imported EJB.

                             /basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
    basicejb-ear-1.0-SNAPSHOT/basicejb-ejb-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
    
  11. Add the WAR to the *root* level module and verify everything builds from the root.

    
        <modules>
            <module>basicejb-ejb</module>
            <module>basicejb-ear</module>
            <module>basicejb-test</module>
            <module>basicejb-war</module>
        </modules>
    
    $ mvn clean install -DskipTests
    ...
    [INFO] Reactor Summary:
    [INFO] 
    [INFO] Basic EJB Exercise ................................. SUCCESS [  0.448 s]
    [INFO] Basic EJB Exercise::EJB ............................ SUCCESS [  2.550 s]
    [INFO] Basic EJB Exercise::EAR ............................ SUCCESS [  0.432 s]
    [INFO] Basic EJB Exercise::Remote Test .................... SUCCESS [  5.934 s]
    [INFO] Basic EJB Exercise::WAR ............................ SUCCESS [  0.458 s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
  12. This is what our project looks like so far.

    .
    |-- basicejb-ear
    |   `-- pom.xml
    |-- basicejb-ejb
    |   |-- pom.xml
    |   `-- src
    |       |-- main
    |       |   `-- java
    |       |       `-- org
    |       |           `-- myorg
    |       |               `-- basicejb
    |       |                   `-- ejb
    |       |                       |-- ReservationEJB.java
    |       |                       |-- ReservationLocal.java
    |       |                       `-- ReservationRemote.java
    |       `-- test
    |           |-- java
    |           |   `-- org
    |           |       `-- myorg
    |           |           `-- basicejb
    |           |               `-- ejb
    |           |                   `-- ReservationTest.java
    |           `-- resources
    |               `-- log4j.xml
    |-- basicejb-test
    |   |-- pom.xml
    |   `-- src
    |       `-- test
    |           |-- java
    |           |   `-- org
    |           |       `-- myorg
    |           |           `-- basicejb
    |           |               `-- earejb
    |           |                   `-- ReservationIT.java
    |           `-- resources
    |               |-- jndi.properties
    |               `-- log4j.xml
    |-- basicejb-war
    |   `-- pom.xml
    `-- pom.xml
    

One advantage the WAR module has over the EAR module is that it can contain production code, unit tests, and IT tests. One could argue that is too much to place into a single module but who wants to be limited in options before we see what mode is best for our application. One could create the RMI Test for the WAR-deployed EJB in a separate module -- but we already did that for the EAR and the steps would be pretty much the same. In this section we will implement the RMI IT test within the WAR itself. This is a reasonable approach for smaller applications.

  1. Add the dependencies to the WAR/pom.xml required to use logging and JUnit.

    
            <!-- core dependencies -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <scope>provided</scope>
            </dependency>

            <!-- test dependencies -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <scope>test</scope>
            </dependency>
  2. Add the dependencies required to be an RMI client of JBoss/Wildfly. We can again leverage the info.ejava.examples.common:jboss-rmi-client dependency to automatically bring these dependencies in.

    
            <!-- dependencies used for remote interface -->
            <dependency>
                <groupId>info.ejava.examples.common</groupId>
                <artifactId>jboss-rmi-client</artifactId>
                <type>pom</type>
                <scope>test</scope>
            </dependency>
  3. Create a JNDI configuration by copying your jndi.properties from the EAR-based RMI Test module. Place this file in src/test/resources of the WAR.

    $ mkdir -p basicejb-war/src/test/resources
    $ cp basicejb-test/src/test/resources/*.properties basicejb-war/src/test/resources/
    
    $ cat basicejb-war/src/test/resources/jndi.properties
    
    #jndi.properties
    java.naming.factory.initial=${java.naming.factory.initial}
    java.naming.factory.url.pkgs=${java.naming.factory.url.pkgs}
    java.naming.provider.url=${java.naming.provider.url}
    #java.naming.security.principal=${jndi.user}
    #java.naming.security.credentials=${jndi.password}
    
  4. Add a log4j.xml file to configure Log4j loggers. You may use a copy of the file you put into your EJB and RMI Test.

    $ cp basicejb-test/src/test/resources/log4j.xml basicejb-war/src/test/resources/
    
    
    $ cat basicejb-test/src/test/resources/log4j.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE log4j:configuration PUBLIC
      "-//APACHE//DTD LOG4J 1.2//EN" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
    <log4j:configuration 
        xmlns:log4j="http://jakarta.apache.org/log4j/" 
        debug="false">
       
        <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
            <param name="Target" value="System.out"/>
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="%d{HH:mm:ss,SSS} %-5p (%F:%L) -%m%n"/>
            </layout>
        </appender>

        <appender name="logfile" class="org.apache.log4j.RollingFileAppender">
            <param name="File" value="target/log4j-out.txt"/>
            <param name="Append" value="false"/>
            <param name="MaxFileSize" value="100KB"/>
            <param name="MaxBackupIndex" value="1"/>
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" 
                       value="%-5p %d{dd-MM HH:mm:ss,SSS} [%c] (%F:%M:%L)  -%m%n"/>
            </layout>
       </appender>

       <logger name="org.myorg">
          <level value="debug"/>
          <appender-ref ref="logfile"/>  
       </logger>
       <root>
          <priority value="info"/>    
          <appender-ref ref="CONSOLE"/>  
       </root>   
    </log4j:configuration>
  5. Add resource filtering to test resources in the WAR/pom.xml. This will cause the jndi.properties file to have variables replaced with physical values when copied to the target tree.

    
            <build>
            <!-- filter test/resource files for profile-specific valies -->
            <testResources>
                <testResource>
                    <directory>src/test/resources</directory>
                    <filtering>true</filtering>
                    <includes>
                        <include>**/*.properties</include>
                    </includes>
                </testResource>
                <testResource>
                    <directory>src/test/resources</directory>
                    <filtering>false</filtering>
                    <excludes>
                        <exclude>**/*.properties</exclude>
                    </excludes>
                </testResource>
            </testResources>
  6. Rebuild just the WAR and verify the two JNDI/EJBClient files were filtered and their variables properly expanded.

    $ mvn clean process-test-resources
    ...
    [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ basicejb-war ---
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 2 resources
    [INFO] Copying 1 resource
    ..
    [INFO] BUILD SUCCESS
    
    $ cat basicejb-war/target/test-classes/jndi.properties
    
    #jndi.properties
    java.naming.factory.initial=org.wildfly.naming.client.WildFlyInitialContextFactory
    java.naming.factory.url.pkgs=
    java.naming.provider.url=http-remoting://127.0.0.1:8080
    #java.naming.security.principal=known
    #java.naming.security.credentials=password1!
  7. Copy your JUnit IT test from the EAR-based RMI Test module and place it in your src/test tree. Use a new package directory (warejb versus earejb) for the copied IT test.

    $ mkdir -p basicejb-war/src/test/java/org/myorg/basicejb/warejb/
    $ cp basicejb-test/src/test/java/org/myorg/basicejb/earejb/ReservationIT.java basicejb-war/src/test/java/org/myorg/basicejb/warejb/ReservationIT.java
    

    Modify the Java package spec to match the new directory. The rest is exactly what we covered in the EAR deploy section.

    package org.myorg.basicejb.warejb; <<<<<<<<<<<<<<<<<<<<<<
    
    
    import static org.junit.Assert.*;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    import org.junit.Before;
    import org.junit.Test;
    import org.myorg.basicejb.ejb.ReservationRemote;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    public class ReservationIT {
        private static final Logger logger = LoggerFactory.getLogger(ReservationIT.class);
        private static final String reservationJNDI = System.getProperty("jndi.name.reservation"); 
        private InitialContext jndi;
        private ReservationRemote reservationist; 
        
        @Before
        public void setUp() throws NamingException {
            assertNotNull("jndi.name.reservation not supplied", reservationJNDI);
            logger.debug("getting jndi initial context");
            jndi=new InitialContext();
            logger.debug("jndi={}", jndi.getEnvironment());
            jndi.lookup("jms");
            
            logger.debug("jndi name:{}", reservationJNDI);
            reservationist = (ReservationRemote) jndi.lookup(reservationJNDI);
            logger.debug("reservationist={}", reservationist);
        }
        
        @Test
        public void testPing() throws NamingException {
            logger.info("*** testPing ***");
            reservationist.ping();
        }
    }
  8. Attempt to build at this point. The IT test will be compiled but not run because we have not yet declared the failsafe plugin in our WAR module to execute the JUnit IT tests.

    $ mvn clean verify
    ...
    [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ basicejb-war ---
    ...
    [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ basicejb-war ---
    ...
    [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ basicejb-war ---
    ...
    [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ basicejb-war ---
    ...
    [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ basicejb-war ---
    [INFO] Changes detected - recompiling the module!
    [INFO] Compiling 1 source file to /home/jcstaff/proj/basicejbEx/basicejb-war/target/test-classes
    ...
    [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ basicejb-war ---
    ...
    [INFO] --- maven-war-plugin:3.2.2:war (default-war) @ basicejb-war ---
    ...
    [INFO] --- cargo-maven2-plugin:1.4.3:redeploy (cargo-prep) @ basicejb-war ---
    ...
    [INFO] --- cargo-maven2-plugin:1.4.3:undeploy (cargo-post) @ basicejb-war ---
    ...
    [INFO] BUILD SUCCESS
    
  9. Declare the failsafe plugin to your WAR/pom.xml to cause our JUnit IT test to be attempted.

    
            <plugins>
                <!-- adds IT integration tests to the build -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-failsafe-plugin</artifactId>
                    <configuration>
                        <systemPropertyVariables>
                            <jndi.name.reservation>ejb:/basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote</jndi.name.reservation>
                        </systemPropertyVariables>
                    </configuration>
                </plugin>

    Remember to pass a jndi.name.reservation system property into the JVM using the failsafe configuration. The JNDI name differs slightly from the EAR-based form and can be obtained from the names printed on the JBoss console or server.log.

    java:jboss/exported/basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
    
  10. Attempt to build at this point. The WAR should be deployed during the pre-integration-test phase, the IT test run during the integration-test phase, the WAR undeployed during the post-integration-test phase, and the results checked for failure during the verify phase.

    $ mvn clean verify
    ...
    [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ basicejb-war ---
    ...
    [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ basicejb-war ---
    ...
    [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ basicejb-war ---
    ...
    [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ basicejb-war ---
    ...
    [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ basicejb-war ---
    ...
    [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ basicejb-war ---
    ...
    [INFO] --- maven-war-plugin:3.2.2:war (default-war) @ basicejb-war ---
    ...
    [INFO] --- cargo-maven2-plugin:1.4.3:redeploy (cargo-prep) @ basicejb-war ---
    ...
    [INFO] --- maven-failsafe-plugin:2.17:integration-test (default) @ basicejb-war ---
    ...
    Running org.myorg.basicejb.warejb.ReservationIT
    ...
    02:30:58,927 DEBUG (ReservationIT.java:26) -jndi={
    java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory, 
    java.naming.provider.url=http-remoting://localhost:8080, 
    java.naming.factory.url.pkgs=org.jboss.ejb.client.naming, 
    jboss.naming.client.ejb.context=true}
    ...
    02:30:59,231 DEBUG (ReservationIT.java:29) -jndi name:ejb:/basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
    02:30:59,247 DEBUG (ReservationIT.java:31) -reservationist=Proxy for remote EJB StatelessEJBLocator{
    appName='', 
    moduleName='basicejb-war-1.0-SNAPSHOT', 
    distinctName='', 
    beanName='ReservationEJB', view='interface org.myorg.basicejb.ejb.ReservationRemote'}
    02:30:59,248 INFO  (ReservationIT.java:36) -*** testPing ***
    ...
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
    ...
    [INFO] --- cargo-maven2-plugin:1.4.3:undeploy (cargo-post) @ basicejb-war ---
    ...
    [INFO] --- maven-failsafe-plugin:2.17:verify (default) @ basicejb-war ---
    ...
    [INFO] BUILD SUCCESS
    

    Note

    Notice the we are now getting a failsafe execution and our JUnit IT test run after the cargo deployment. We get this because we added a declaration of the failsafe plugin to the WAR module *and* we ended the Java class with IT. Looking at the plugin page, the other default name patterns include.

    • **/IT*.java

    • **/*IT.java

    • **/*ITCase.java

    As discussed on that same web page -- you can expand or shrink that list with the use of includes and excludes. This is commonly done to focus your testing around a specific IT test or to exclude a IT test that requires further work for later.

  11. Verify that everything builds from the root module.

    $ cd ..; mvn clean install
    ...
    [INFO] Basic EJB Exercise ................................. SUCCESS [  0.178 s]
    [INFO] Basic EJB Exercise::EJB ............................ SUCCESS [  3.014 s]
    [INFO] Basic EJB Exercise::EAR ............................ SUCCESS [  0.412 s]
    [INFO] Basic EJB Exercise::Remote Test .................... SUCCESS [  5.144 s]
    [INFO] Basic EJB Exercise::WAR ............................ SUCCESS [  2.571 s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    

In the previous section we deployed an EJB imported from an external EJB Module. In this section we will embed a new EJB within the WAR module. This type of packaging can be used by JSP/Servlet writers to invoke lightweight POJOs injected by the container that can form transaction boundaries and other EJB functionality -- without having to create a separate EJB module. The more you think of this as a "for internal use only" and the smaller/self-contained your application -- the more this packaging scheme makes sense even though it can also blur the boundaries between the architectural layers.

  1. Create a source directory for Java classes that will be included in the production WAR. This source is placed in src/main/java but will end up in WEB-INF/classes once the WAR is built.

    $ mkdir -p basicejb-war/src/main/java
    
  2. Create a package directory for our EJB and interface classes.

    $ mkdir -p basicejb-war/src/main/java/org/myorg/basicejb/webejb/
    
  3. Create the following EJB @Remote interface in the WAR/src/main/java directory.

    $ cat basicejb-war/src/main/java/org/myorg/basicejb/webejb/ShopperRemote.java
    
    
    package org.myorg.basicejb.webejb;
    import javax.ejb.Remote;
    @Remote
    public interface ShopperRemote {
        int ping();
        void close();    
    }
  4. Create the following Stateful EJB class in the WAR/src/main/java directory. In the previous example our ReservationEJB was stateless and could not maintain any conversation state with the client. In this example we will make the EJB stateful -- which means there will be a memory allocated for each client to house information specific to their conversation with the EJB instance.

    $ cat basicejb-war/src/main/java/org/myorg/basicejb/webejb/ShopperEJB.java
    
    package org.myorg.basicejb.webejb;
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    import javax.ejb.Remove;
    import javax.ejb.Stateful;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    @Stateful
    public class ShopperEJB implements ShopperRemote {
        private static Logger logger = LoggerFactory.getLogger(ShopperEJB.class);
        
        //we can only track conversation state here if we are stateful
        private int counter=0;
        @PostConstruct
        public void init() {
            logger.debug("*** ShopperEJB({}).init() ***", super.hashCode());
        }
        
        @PreDestroy
        public void destroy() {
            logger.debug("*** ShopperEJB({}).destroy() ***", super.hashCode());
        }
        
        @Override
        public int ping() {
            logger.debug("ping({}) called, returned {}", super.hashCode(), counter);
            return counter++;
        }
        @Override
        @Remove
        public void close() {
            logger.debug("close({}) called", super.hashCode());        
        }
    }
  5. Deploy the WAR at this point so we can be sure the EJB was created correctly and to be sure of the JNDI name created. Note that we are not yet done with the module. The problem is we have transitioned from a IT test-only module to one that also hosts EJB code. We are missing a few dependencies.

    $ mvn clean pre-integration-test
    
    ...
    [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ basicejb-war ---
    [INFO] Changes detected - recompiling the module!
    [INFO] Compiling 2 source files to /home/jcstaff/proj/basicejbEx/basicejb-war/target/classes
    [INFO] -------------------------------------------------------------
    [ERROR] COMPILATION ERROR : 
    [INFO] -------------------------------------------------------------
    [ERROR] /home/jcstaff/proj/basicejbEx/basicejb-war/src/main/java/org/myorg/basicejb/webejb/ShopperEJB.java:[6,17] package javax.ejb does not exist
    [ERROR] /home/jcstaff/proj/basicejbEx/basicejb-war/src/main/java/org/myorg/basicejb/webejb/ShopperRemote.java:[3,17] package javax.ejb does not exist
    [ERROR] /home/jcstaff/proj/basicejbEx/basicejb-war/src/main/java/org/myorg/basicejb/webejb/ShopperRemote.java:[5,2] cannot find symbol
      symbol: class Remote
    [ERROR] /home/jcstaff/proj/basicejbEx/basicejb-war/src/main/java/org/myorg/basicejb/webejb/ShopperEJB.java:[11,2] cannot find symbol
      symbol: class Stateful
    [INFO] 4 errors 
    ...
    [INFO] BUILD FAILURE
    
  6. Add several dependencies to the WAR/pom.xml account for use of EJB types.

    
    # basicejb-war/pom.xml
            <!-- for EJBs embedded in WAR module -->
            <dependency>
                <groupId>javax.ejb</groupId>
                <artifactId>javax.ejb-api</artifactId>
                <scope>provided</scope>
            </dependency>
  7. Re-deploy the WAR with the compilation dependency corrected and look for the newly added EJB to show up in the console or server.log.

    $ mvn clean pre-integration-test
    ...
    [INFO] BUILD SUCCESS
    
            java:global/basicejb-war-1.0-SNAPSHOT/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote
            java:app/basicejb-war-1.0-SNAPSHOT/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote
            java:module/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote
            java:jboss/exported/basicejb-war-1.0-SNAPSHOT/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote
            java:global/basicejb-war-1.0-SNAPSHOT/ShopperEJB
            java:app/basicejb-war-1.0-SNAPSHOT/ShopperEJB
            java:module/ShopperEJB
    

    We are specially interested in the java:jboss/exported name.

    java:jboss/exported/basicejb-war-1.0-SNAPSHOT/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote
    

    This will form the base of our JNDI name used for the IT test.

    /basicejb-war-1.0-SNAPSHOT/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote
    

    The main difference between the stateless and stateful JNDI names are when we use EJB Client. When using the "ejb:" naming prefix we must append "?stateful" to the end of the name to tell EJB Client we are communicating with a stateful EJB. EJB Client, unlike JBoss Remoting, understands EJB communication and will attempt to setup for communication with the EJB in an efficient manner. The extra text is not appropriate for using outside of the "ejb:" naming.

    ejb:/basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote
    ejb:/basicejb-war-1.0-SNAPSHOT/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote?stateful
    
  8. Create the following IT test in your WAR/src/test/java directory tree.

    $ cat basicejb-war/src/test/java/org/myorg/basicejb/warejb/ShopperIT.java
    
    
    package org.myorg.basicejb.warejb;
    import static org.junit.Assert.*;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    import org.junit.Before;
    import org.junit.Test;
    import org.myorg.basicejb.webejb.ShopperRemote;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    public class ShopperIT {
        private static final Logger logger = LoggerFactory.getLogger(ShopperIT.class);
        private static final String shopperJNDI = System.getProperty("jndi.name.shopper"); 
        private InitialContext jndi;
        
        @Before
        public void setUp() throws NamingException {
            assertNotNull("jndi.name.reservation not supplied", shopperJNDI);
            logger.debug("getting jndi initial context");
            jndi=new InitialContext();
            logger.debug("jndi={}", jndi.getEnvironment());
        }
        
        @Test
        public void testPing() throws NamingException {
            logger.info("*** testPing ***");
            ShopperRemote shopper1=null;
            try {
                shopper1= (ShopperRemote) jndi.lookup(shopperJNDI);
                for (int i=0; i<10; i++) {
                    int counter1=shopper1.ping();
                    assertEquals("unexpected count from shopper1",  i, counter1);
                }
            } finally {
                if (shopper1!=null) { shopper1.close(); }
            }
        }
    }
  9. Add the new EJB's jndi name to the failsafe configuration using the "jndi.name.shopper" token found in the IT class' System.getProperty() statement. Remember to add the "?stateful" to the end of the JNDI name. This will help the client library setup appropriately for communication with this EJB.

    
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <configuration>
            <systemPropertyVariables>
                <jndi.name.reservation>ejb:/basicejb-war-1.0-SNAPSHOT/ReservationEJB!org.myorg.basicejb.ejb.ReservationRemote</jndi.name.reservation>
                <jndi.name.shopper>ejb:/basicejb-war-1.0-SNAPSHOT/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote?stateful</jndi.name.shopper>
            </systemPropertyVariables>
        </configuration>
    </plugin>
  10. Build the WAR and note our IT test passes -- verifying the stateful behavior of the EJB. Note the output in the server.log. It shows each call returning to the same bean instance.

    $ mvn clean verify
    ...
    Running org.myorg.basicejb.warejb.ReservationIT
    ...
    Running org.myorg.basicejb.warejb.ShopperIT
    ...
    Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
    
    ...
    [INFO] BUILD SUCCESS
    
    2018-08-19 17:30:21,688 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) *** ShopperEJB(1087164002).init() ***
    2018-08-19 17:30:21,697 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) ping(1087164002) called, returned 0
    2018-08-19 17:30:21,701 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) ping(1087164002) called, returned 1
    ...
    2018-08-19 17:30:21,737 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) ping(1087164002) called, returned 9
    2018-08-19 17:30:21,741 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) close(1087164002) called
    2018-08-19 17:30:21,742 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) *** ShopperEJB(1087164002).destroy() ***
    
  11. Update the IT test to add a second instance used concurrently with the first.

    @Test
    
    public void testPing() throws NamingException {
        logger.info("*** testPing ***");
        ShopperRemote shopper1=null;
        ShopperRemote shopper2=null;
        try {
            shopper1= (ShopperRemote) jndi.lookup(shopperJNDI);
            shopper2= (ShopperRemote) jndi.lookup(shopperJNDI);
            for (int i=0; i<10; i++) {
                int counter1=shopper1.ping();
                int counter2=shopper2.ping();
                assertEquals("unexpected count from shopper1",  i, counter1);
                assertEquals("unexpected count from shopper2",  i, counter2);
            }
        } finally {
            if (shopper1!=null) { shopper1.close(); }
            if (shopper2!=null) { shopper2.close(); }           
        }
    }
  12. Re-build the WAR and note the IT test continues to pass -- showing we have two independent instances. Note the output in the server.log showing the two sets of calls went to separate, consistent instances on the server.

    $ mvn clean verify
    ...
    Running org.myorg.basicejb.warejb.ReservationIT
    ...
    Running org.myorg.basicejb.warejb.ShopperIT
    ...
    Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
    
    ...
    [INFO] BUILD SUCCESS
    
    2018-08-19 17:37:20,686 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-2) *** ShopperEJB(1430132394).init() ***
    2018-08-19 17:37:20,688 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) *** ShopperEJB(1087164002).init() ***
    2018-08-19 17:37:20,694 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-2) ping(1430132394) called, returned 0
    2018-08-19 17:37:20,697 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) ping(1087164002) called, returned 0
    2018-08-19 17:37:20,699 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-2) ping(1430132394) called, returned 1
    2018-08-19 17:37:20,701 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) ping(1087164002) called, returned 1
    ...
    2018-08-19 17:37:20,735 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-2) ping(1430132394) called, returned 9
    2018-08-19 17:37:20,737 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) ping(1087164002) called, returned 9
    2018-08-19 17:37:20,739 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-2) close(1430132394) called
    2018-08-19 17:37:20,739 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-2) *** ShopperEJB(1430132394).destroy() ***
    2018-08-19 17:37:20,741 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) close(1087164002) called
    2018-08-19 17:37:20,742 DEBUG [org.myorg.basicejb.webejb.ShopperEJB] (default task-1) *** ShopperEJB(1087164002).destroy() ***
    
  13. Take a look at the produced WAR. Notice it contains the imported EJB archive in the WEB-INF/lib directory and embedded EJB classes in WEB-INF/classes directory. These are standard locations in a WAR for deploying executable code in the WAR's classloader.

    
    
    $ jar tf basicejb-war/target/basicejb-war-1.0-SNAPSHOT.war 
    ...
    WEB-INF/lib/basicejb-ejb-1.0-SNAPSHOT.jar
    WEB-INF/classes/org/myorg/basicejb/webejb/ShopperRemote.class
    WEB-INF/classes/org/myorg/basicejb/webejb/ShopperEJB.class
    ...
  14. Now that you have this working, experiment by removing the "?stateful" from the JNDI name in the WAR/pom.

    
    <jndi.name.shopper>ejb:/basicejb-war-1.0-SNAPSHOT/ShopperEJB!org.myorg.basicejb.webejb.ShopperRemote</jndi.name.shopper>
  15. Attempt to build and IT test your stateless EJB. Needless to say you will not get beyind the first call to ping where the client library is trying to broker something in the call.

    [INFO] Running org.myorg.basicejb.warejb.ShopperIT
    17:23:30,284 DEBUG (ShopperIT.java:24) -getting jndi initial context
    17:23:30,285 DEBUG (ShopperIT.java:26) -jndi={java.naming.factory.initial=org.wildfly.naming.client.WildFlyInitialContextFactory, java.naming.provider.url=http-remoting://127.0.0.1:8080, java.naming.factory.url.pkgs=}
    17:23:30,286 INFO  (ShopperIT.java:31) -*** testPing ***
    [ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.098 s <<< FAILURE! - in org.myorg.basicejb.warejb.ShopperIT
    [ERROR] testPing(org.myorg.basicejb.warejb.ShopperIT)  Time elapsed: 0.098 s  <<< ERROR!
    javax.ejb.EJBException: java.lang.IllegalStateException: WFLYEJB0234: Session id hasn't been set for stateful component: ShopperEJB
    Caused by: java.lang.IllegalStateException: WFLYEJB0234: Session id hasn't been set for stateful component: ShopperEJB
    ...
    [ERROR] Errors:
    [ERROR]   ShopperIT.testPing » EJB java.lang.IllegalStateException: WFLYEJB0234: Session...
    
    Tests run: 2, Failures: 0, Errors: 1, Skipped: 0
    [INFO] BUILD FAILURE
    
  16. Restore the JNDI name and verify your project tree looks like the following at this point.

    .
    |-- basicejb-ear
    |   `-- pom.xml
    |-- basicejb-ejb
    |   |-- pom.xml
    |   `-- src
    |       |-- main
    |       |   `-- java
    |       |       `-- org
    |       |           `-- myorg
    |       |               `-- basicejb
    |       |                   `-- ejb
    |       |                       |-- ReservationEJB.java
    |       |                       |-- ReservationLocal.java
    |       |                       `-- ReservationRemote.java
    |       `-- test
    |           |-- java
    |           |   `-- org
    |           |       `-- myorg
    |           |           `-- basicejb
    |           |               `-- ejb
    |           |                   `-- ReservationTest.java
    |           `-- resources
    |               `-- log4j.xml
    |-- basicejb-test
    |   |-- pom.xml
    |   `-- src
    |       `-- test
    |           |-- java
    |           |   `-- org
    |           |       `-- myorg
    |           |           `-- basicejb
    |           |               `-- earejb
    |           |                   `-- ReservationIT.java
    |           `-- resources
    |               |-- jndi.properties
    |               `-- log4j.xml
    |-- basicejb-war
    |   |-- pom.xml
    |   `-- src
    |       |-- main
    |       |   |-- java
    |       |   |   `-- org
    |       |   |       `-- myorg
    |       |   |           `-- basicejb
    |       |   |               `-- webejb
    |       |   |                   |-- ShopperEJB.java
    |       |   |                   `-- ShopperRemote.java
    |       |   `-- webapp
    |       `-- test
    |           |-- java
    |           |   `-- org
    |           |       `-- myorg
    |           |           `-- basicejb
    |           |               `-- warejb
    |           |                   |-- ReservationIT.java
    |           |                   `-- ShopperIT.java
    |           `-- resources
    |               |-- jndi.properties
    |               `-- log4j.xml
    `-- pom.xml