Enterprise Java Development@TOPIC@

Chapter 47. Multi-Module JavaEE Project

47.1. Purpose
47.1.1. Goals
47.1.2. Objectives
47.2. Create Root Module
47.3. Create EJB Module
47.4. Manage Application Server
47.4.1. Application Server Setup
47.4.2. Standalone Application Server
47.4.3. Embedded Application Server
47.5. Summary

Each component of the Java EE application will be developed as as a separate Maven module. Each module will be placed in a flat structure under a common parent project. This parent project will be used to coordinate goals involved of the entire application. The order in which it builds things can be influenced by the configuration you supply, however, maven will analyze dependencies at build time and either honor the actual dependency ordering or fail if you have expressed a circular dependency.

  1. Create a root directory for the exercise. This will host the root project and sub-projects for the Impl, EJB, WAR, EAR, and RMI Test modules.

    $ mkdir ejb-basichotel
    
  2. Create a root project pom.xml file. This project will will not have an associated artifact and is termed a "parent" (simple term) or "reactor" (techy term) project. It uses a special packaging type called "pom".

    This project will also be used as a parent project of all implementation modules so we can define re-usable definitions. Note that the definitions within this project are passive. They will not actively add any dependencies or plugins to inheriting child modules unless the child specifically references the defined artifact. Details for the potentially used artifact can be placed here (and consistently reused) and briefly referenced in the child poms or inherently referenced by the child modules packaging type (i.e., packaging=jar projects automatically bring in the maven-compiler-plugin)

    
    <?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>myorg.basicejb</groupId>
        <artifactId>basicejbEx</artifactId>
        <packaging>pom</packaging>
        <name>Basic EJB Exercise</name>
        <version>1.0-SNAPSHOT</version>
        <description>
            This project is the root project for the example Java EE
            Application.
        </description>
        <modules>
        </modules>

        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>

        <repositories>
        </repositories>
        <pluginRepositories>
        </pluginRepositories>

        <dependencyManagement>
            <dependencies>
            </dependencies>
        </dependencyManagement>

        <build>
            <pluginManagement>
                <plugins>
                </plugins>
            </pluginManagement>
        </build>

        <profiles>
        </profiles>
    </project>
  3. Add in the maven-compiler-plugin specification to the parent pom to make sure JDK 1.7 or above is used and we use an up to date version of the plugin.

    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <java.source.version>1.8</java.source.version>
            <java.target.version>1.8</java.target.version>

            <maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
        </properties>
    ...
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>${maven-compiler-plugin.version}</version>
                        <configuration>
                                <source>${java.source.version}</source>
                                <target>${java.target.version}</target>
                        </configuration>                    
                    </plugin>
  4. Test your root project by building it at this time.

    $mvn clean install
    ...
    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] ------------------------------------------------------------------------
    [INFO] Building Basic EJB Exercise 1.0-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    [INFO] 
    [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ basicejbEx ---
    [INFO] 
    [INFO] --- maven-install-plugin:2.4:install (default-install) @ basicejbEx ---
    [INFO] Installing /home/jcstaff/proj/basicejbEx/pom.xml to /home/jcstaff/.m2/repository2/myorg/basicejb/basicejbEx/1.0-SNAPSHOT/basicejbEx-1.0-SNAPSHOT.pom
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 0.682 s
    [INFO] Finished at: 2014-10-04T17:45:26-04:00
    [INFO] Final Memory: 7M/105M
    [INFO] ------------------------------------------------------------------------
    

The EJB module will be used to develop one of the EJB components. The term "EJB" gets a little overloaded at times. There are EJB classes, EJB components, and an EJB tier. EJB classes break out into the business-remote (aka @Remote) and business-local (aka @Local) interfaces, the EJB implementation class (either @Stateless, @Stateful, @Singleton, or @MessageDriven), and support classes. It is common to think of each cohesive pairing of @Remote, @Local, and implementation class as "an EJB". You can have many EJBs (the sets of classes) within an EJB component. An EJB component is a materialized as a .jar and there can be many EJB components within your EJB tier. For this exercise we will have only one EJB component and start out with only one EJB. A second EJB will be added to the single EJB component in a later excercise to help support testing. A single Maven project can build a single EJB component.

  1. Add an EJB module directory to your project tree.

    $ mkdir basicejb-ejb
    
  2. Add the outer shell of the EJB module's 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>
            <artifactId>basicejbEx</artifactId>
            <groupId>myorg.basicejb</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>

        <artifactId>basicejb-ejb</artifactId>
        <packaging>ejb</packaging>
        <name>Basic EJB Exercise::EJB</name>
        <description>
            This project provides example usages of an EJB tier.
        </description>

        <dependencies>
        </dependencies>

        <build>
            <plugins>
            </plugins>
        </build>
    </project>
  3. Add in the maven-ejb-plugin definition to the *parent* pom.xml with all properties and constructs that would be common across all EJB modules.

    
    # basicejbEx/pom.xml

        <properties>
    ...
            <maven-ejb-plugin.version>3.0.1</maven-ejb-plugin.version>
        </properties>


            <pluginManagement>
    ...
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-ejb-plugin</artifactId>
                        <version>${maven-ejb-plugin.version}</version>
                        <configuration>
                            <ejbVersion>3.2</ejbVersion>
                            <archive>
                                <manifest>
                                    <addClasspath>true</addClasspath>
                                </manifest>
                            </archive>
                        </configuration>
                    </plugin>
  4. Add in the maven-ejb-plugin declaration to the EJB/pom.xml. This will contain portions of the plugin definition that could be unique per EJB module.

    
            <!-- tell the EJB plugin to build a client-jar -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-ejb-plugin</artifactId>
                <configuration>
                    <generateClient>true</generateClient>
                    <clientExcludes>
                        <clientExclude>**/META-INF/*.xml</clientExclude>
                        <clientExclude>**/ejb/*EJB.class</clientExclude>
                    </clientExcludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
  5. Add several dependencies to the EJB/pom.xml account for use of logging, annotations, and EJB constructs. Since we will be instantiating this code only on the server and not in a 2-tier approach, the pure JavaEE API from Sun/Oracle will do fine. Otherwise we should use the dependencies from Hibernate/JBoss.

    
    # basicejbEx/basicejb-ejb/pom.xml

        <dependencies>
            <!-- core implementation dependencies -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>javax.ejb</groupId>
                <artifactId>javax.ejb-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>    
        </dependencies>
  6. Attempt to validate the EJB module at the child level without futher specification. This will fail because we are missing version information for the two dependency artifacts we just added.

    $ mvn validate
    [ERROR]   The project myorg.basicejb:basicejb-ejb:1.0-SNAPSHOT (/home/jcstaff/proj/basicejbEx/basicejb-ejb/pom.xml) has 5 errors
    [ERROR]     'dependencies.dependency.version' for org.slf4j:slf4j-api:jar is missing. @ myorg.basicejb:basicejb-ejb:[unknown-version], .../basicejbEx/basicejb-ejb/pom.xml, line 22, column 21
    [ERROR]     'dependencies.dependency.version' for javax.ejb:javax.ejb-api:jar is missing. @ myorg.basicejb:basicejb-ejb:[unknown-version], .../basicejbEx/basicejb-ejb/pom.xml, line 27, column 21
    [ERROR]     'dependencies.dependency.version' for junit:junit:jar is missing. @ myorg.basicejb:basicejb-ejb:[unknown-version], .../basicejbEx/basicejb-ejb/pom.xml, line 34, column 21
    [ERROR]     'dependencies.dependency.version' for org.slf4j:slf4j-log4j12:jar is missing. @ myorg.basicejb:basicejb-ejb:[unknown-version], .../basicejbEx/basicejb-ejb/pom.xml, line 39, column 21
    [ERROR]     'dependencies.dependency.version' for log4j:log4j:jar is missing. @ myorg.basicejb:basicejb-ejb:[unknown-version], .../basicejbEx/basicejb-ejb/pom.xml, line 44, column 21
    
  7. Update the parent pom.xml with the version specifications for these artifacts.

    
    # basicejbEx/pom.xml

        <properties>
            ...
            <javax.ejb-api.version>3.2.2</javax.ejb-api.version>
            <junit.version>4.12</junit.version>
            <slf4j.version>1.7.25</slf4j.version>
            <log4j.version>1.2.17</log4j.version>
    ...
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                    <version>${slf4j.version}</version>
                </dependency>
                <dependency>
                    <groupId>javax.ejb</groupId>
                    <artifactId>javax.ejb-api</artifactId>
                    <version>${javax.ejb-api.version}</version>
                </dependency>
                <dependency>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                    <version>${junit.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                    <version>${slf4j.version}</version>
                </dependency>
                <dependency>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                    <version>${log4j.version}</version>
                </dependency>    
            </dependencies>
        </dependencyManagement>
  8. Validate that maven can now resolve the new dependencies.

    $ mvn validate
    
    [INFO] BUILD SUCCESS
    
  9. Create the src tree for the EJB.

    $ mkdir mkdir -p basicejb-ejb/src/main/java/org/myorg/basicejb/ejb
    
  10. Add @Remote and @Local interfaces for the EJB. Place a simple ping() method in the @Remote interface for use as an end-to-end sanity check at the end of this exercise.

    $ cat basicejb-ejb/src/main/java/org/myorg/basicejb/ejb/ReservationRemote.java
    
    package org.myorg.basicejb.ejb;
    import javax.ejb.Remote;
    @Remote
    public interface ReservationRemote {
        void ping();
    }
    $ cat basicejb-ejb/src/main/java/org/myorg/basicejb/ejb/ReservationLocal.java
    
    package org.myorg.basicejb.ejb;
    import javax.ejb.Local;
    @Local
    public interface ReservationLocal {
    }
  11. Add a @Stateless EJB that will implement the provided @Remote and @Local interfaces. Implement @PostConstruct and @Destroy callbacks to intercept EJB lifecycle events. Add a logger and log statements to the methods so we can observe activity within the EJB during the test at the end of this exercise.

    $ cat basicejb-ejb/src/main/java/org/myorg/basicejb/ejb/ReservationEJB.java
    
    package org.myorg.basicejb.ejb;
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    import javax.ejb.Stateless;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    @Stateless
    public class ReservationEJB implements ReservationLocal, ReservationRemote {
        private static Logger logger = LoggerFactory.getLogger(ReservationEJB.class);
        @PostConstruct
        public void init() {
            logger.debug("*** ReservationEJB.init() ***");
        }
        
        @PreDestroy
        public void destroy() {
            logger.debug("*** ReservationEJB.destroy() ***");
        }
        
        @Override
        public void ping() {
            logger.debug("ping called");
        }
    }
  12. The EJB can be built at this time. You will notice the following in the output.

    $ mvn package
    [INFO] ------------------------------------------------------------------------
    [INFO] Building Basic EJB Exercise::EJB 1.0-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    ...
    [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ basicejb-ejb ---
    ...
    [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ basicejb-ejb ---
    [INFO] Changes detected - recompiling the module!
    [INFO] Compiling 3 source files to /home/jcstaff/proj/basicejbEx/basicejb-ejb/target/classes
    ...
    [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ basicejb-ejb ---
    ...
    [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ basicejb-ejb ---
    ...
    [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ basicejb-ejb ---
    ...
    [INFO] --- maven-ejb-plugin:2.4:ejb (default-ejb) @ basicejb-ejb ---
    [INFO] Building EJB basicejb-ejb-1.0-SNAPSHOT with EJB version 3.2
    [INFO] Building jar: .../basicejbEx/basicejb-ejb/target/basicejb-ejb-1.0-SNAPSHOT.jar
    [INFO] Building EJB client basicejb-ejb-1.0-SNAPSHOT-client
    [INFO] Building jar: .../basicejbEx/basicejb-ejb/target/basicejb-ejb-1.0-SNAPSHOT-client.jar
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    

    Module structure looks very similar to a POJO/JAR module

    .
    |-- basicejb-ejb
    |   |-- pom.xml
    |   |-- src
    |   |   `-- main
    |   |       `-- java
    |   |           `-- org
    |   |               `-- myorg
    |   |                   `-- basicejb
    |   |                       `-- ejb
    |   |                           |-- ReservationEJB.java
    |   |                           |-- ReservationLocal.java
    |   |                           `-- ReservationRemote.java
    |   `-- target
    |       |-- basicejb-ejb-1.0-SNAPSHOT-client.jar
    |       |-- basicejb-ejb-1.0-SNAPSHOT.jar
    ...
    `-- pom.xml
    

    The EJB.jar contains the full set of EJB classes/interfaces

    $ jar tf target/basicejb-ejb-1.0-SNAPSHOT.jar
    ...
    org/myorg/basicejb/ejb/ReservationLocal.class
    org/myorg/basicejb/ejb/ReservationRemote.class
    org/myorg/basicejb/ejb/ReservationEJB.class
    ...
    

    The EJB-client.jar contains classes/interfaces required by remote clients

    $ jar tf target/basicejb-ejb-1.0-SNAPSHOT-client.jar
    ...
    org/myorg/basicejb/ejb/ReservationLocal.class
    org/myorg/basicejb/ejb/ReservationRemote.class
    ...
    
  13. Add the EJB module to the root pom.xml.

    
        <modules>
            <module>basicejb-ejb</module>
        </modules>
  14. Retest the build from the root.

    [INFO] Scanning for projects...
    ...
    [INFO] ------------------------------------------------------------------------
    [INFO] Reactor Summary:
    [INFO] 
    [INFO] Basic EJB Exercise ................................. SUCCESS [  0.503 s]
    [INFO] Basic EJB Exercise::EJB ............................ SUCCESS [  2.457 s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    
  15. Add a unit test to the EJB module to show that you can unit test functionality that does not require the container. This class will go into the src/test tree and will not be a part of the EJB.jar

    $ mkdir -p basicejb-ejb/src/test/java/org/myorg/basicejb/ejb
    
    $ cat basicejb-ejb/src/test/java/org/myorg/basicejb/ejb/ReservationTest.java
    
    package org.myorg.basicejb.ejb;
    import org.junit.Before;
    import org.junit.Test;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    public class ReservationTest {
        private static final Logger logger = LoggerFactory.getLogger(ReservationTest.class);
        
        ReservationRemote reservatist;
        
        @Before
        public void setUp() {
            reservatist=new ReservationEJB();
        }
        
        @Test
        public void testPing() {
            logger.info("*** testPing ***");
            reservatist.ping();
        }
    }
  16. Add a log4j.xml file to support logging unit test functionality that does not require the container.

    $ mkdir -p basicejb-ejb/src/test/resources
    
    
    $ cat basicejb-ejb/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>
  17. Rebuild the EJB and/or entire application. You will notice the unit tests executed during the build.

    $ mvn clean install
    ...
    [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ basicejb-ejb ---
    [INFO] Surefire report directory: /home/jcstaff/proj/basicejbEx/basicejb-ejb/target/surefire-reports
    
    -------------------------------------------------------
     T E S T S
    -------------------------------------------------------
    Running org.myorg.basicejb.ejb.ReservationTest
    21:50:10,382 INFO  (ReservationTest.java:20) -*** testPing ***
    21:50:10,390 DEBUG (ReservationEJB.java:26) -ping called
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.218 sec
    
    Results :
    
    Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
    
    ...
    [INFO] BUILD SUCCESS
    

    Note

    Notice the we are automatically getting a surefire execution and our JUnit test run. We get this because surefire is automatically brought in by the pom's packaging type *and* we ended the Java class name with Test. Looking at the plugin page, the other default name patterns include.

    • **/Test*.java

    • **/*Test.java

    • **/*TestCase.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 unit test or to temporarily exclude all unit tests from the build to focus on a specific IT test (discussed later).

  18. Verify this is what you have so far.

    .
    |-- 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
    `-- pom.xml
    

This set of steps is in preparation for the next chapter where you will be asked to deploy the EJB built above to the server. In this section you will be shown how to start/stop a standalone server and how to setup, start/stop an embedded server.

The standalone application server runs as a separate process either in the same machine as or remote machine from the development machine. This approach more closely resembles the target server setup and can help test certain production-like configurations. This is normally managed through scripts.

  1. Start the JBoss/Wildfly application server. In the command shown below

    wildfly-13.0.0.Final$ ./bin/standalone.sh
    =========================================================================
    
      JBoss Bootstrap Environment
    
      JBOSS_HOME: /Users/jim/apps/wildfly-13.0.0.Final
    
      JAVA: java
    
      JAVA_OPTS:  -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
    
    =========================================================================
    
    10:14:01,139 INFO  [org.jboss.modules] (main) JBoss Modules version 1.8.5.Final
    10:14:01,372 INFO  [org.jboss.msc] (main) JBoss MSC version 1.4.2.Final
    10:14:01,379 INFO  [org.jboss.threads] (main) JBoss Threads version 2.3.2.Final
    10:14:01,474 INFO  [org.jboss.as] (MSC service thread 1-2) WFLYSRV0049: 
        WildFly Full 13.0.0.Final (WildFly Core 5.0.0.Final) starting
    ...
    10:14:03,969 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: 
        WildFly Full 13.0.0.Final (WildFly Core 5.0.0.Final) started in 3104ms 
        - Started 358 of 581 services (356 services are lazy, passive or on-demand)
    
  2. Shutdown the server using the command line interface (CLI)

    wildfly-13.0.0.Final$ ./bin/jboss-cli.sh --connect command=:shutdown
    {
        "outcome" => "success",
        "result" => undefined
    }
  3. Restart the server.

    wildfly-13.0.0.Final]$ ./bin/standalone.sh 
    ...
    
  4. Shutdown the server by pressing Control-C keys

    ^C10:17:19,557 INFO  [org.jboss.as.server] (Thread-2) WFLYSRV0236: Suspending server with no timeout.
    10:17:19,558 INFO  [org.jboss.as.ejb3] (Thread-2) WFLYEJB0493: EJB subsystem suspension complete
    10:17:19,559 INFO  [org.jboss.as.server] (Thread-2) WFLYSRV0220: Server shutdown has been requested via an OS signal
    ...
    10:17:19,616 INFO  [org.jboss.as] (MSC service thread 1-1) WFLYSRV0050: WildFly Full 13.0.0.Final (WildFly Core 5.0.0.Final) stopped in 53ms