Building a standalone executeable JAR with OpenEJB
Asked Answered
M

2

15

I am building a CLI tool, which integrates with several EJB modules. For this reason, I need to build a fat jar, which is then executed as a standalone application.

However, executing this fat jar with java -jar (Note: conf/openejb.xml is in the same directory as the fat jar) fails with the following stacktrace:

INFORMATION - PersistenceUnit(name=demo, provider=org.hibernate.jpa.HibernatePersistenceProvider) - provider time 2706ms
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/MEJB!javax.management.j2ee.ManagementHome")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/MEJB")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/openejb/Deployer!org.apache.openejb.assembler.Deployer")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/openejb/Deployer")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/openejb/ConfigurationInfo!org.apache.openejb.assembler.classic.cmd.ConfigurationInfo")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/openejb/ConfigurationInfo")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/DemoServiceImpl!com.github.rzo1.service.DemoService")
INFORMATION - Jndi(name="java:global/DemoMain/demo-shade-1.0-SNAPSHOT/DemoServiceImpl")
INFORMATION - Existing thread singleton service in SystemInstance(): org.apache.openejb.cdi.ThreadSingletonServiceImpl@557c8e7e
INFORMATION - Closing DataSource: demoDS
INFORMATION - Closing DataSource: demoDSNonJTA
Exception in thread "Thread-0" java.lang.RuntimeException: org.apache.openejb.OpenEjbContainer$AssembleApplicationException: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
       at com.github.rzo1.DemoMain.run(DemoMain.java:116)
       at java.lang.Thread.run(Thread.java:745)
Caused by: org.apache.openejb.OpenEjbContainer$AssembleApplicationException: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
       at org.apache.openejb.OpenEjbContainer$Provider.createEJBContainer(OpenEjbContainer.java:346)
       at javax.ejb.embeddable.EJBContainer.createEJBContainer(EJBContainer.java:56)
       at com.github.rzo1.DemoMain.run(DemoMain.java:90)
       ... 1 more
Caused by: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
       at org.apache.openejb.cdi.ThreadSingletonServiceImpl.initialize(ThreadSingletonServiceImpl.java:191)
       at org.apache.openejb.cdi.CdiBuilder.build(CdiBuilder.java:41)
       at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:913)
       at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:717)
       at org.apache.openejb.OpenEjbContainer$Provider.createEJBContainer(OpenEjbContainer.java:342)
       ... 3 more
Caused by: org.apache.webbeans.exception.WebBeansException: Wrong startup object.
       at org.apache.webbeans.web.lifecycle.WebContainerLifecycle.getServletContext(WebContainerLifecycle.java:227)
       at org.apache.webbeans.web.lifecycle.WebContainerLifecycle.startApplication(WebContainerLifecycle.java:86)
       at org.apache.openejb.cdi.ThreadSingletonServiceImpl.initialize(ThreadSingletonServiceImpl.java:189)
       ... 7 more

Executing the code directly from my IDE (IntelliJ) the standalone container comes up and behaves as expected.

Version Summary:

  • openejb in version 1.7.0 / openejb-server in version 7.0.2
  • maven-shade-plugin in version 2.4.3
  • Maven in version 3.3.9
  • hibernate in version 5.2.7

Basic Setup

I was able to reproduce my problem on a simple working example, which I added as an GitHub project for further investigation.

The basic project layout is as follows:

  | # demo-shade
    | - demo-services (EJB-Module)          
    | - demo-main (Shading happens here)

The configuration of the maven-shade-plugin is as follows:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.4.3</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <finalName>demo-shade-${project.version}</finalName>
                <transformers>
                    <transformer
                            implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <manifestEntries>
                            <Main-Class>com.github.rzo1.DemoMain</Main-Class>
                        </manifestEntries>
                    </transformer>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/openwebbeans/openwebbeans.properties</resource>
                    </transformer>
                </transformers>
                <filters>
                    <filter> <!-- we don't want JSF to be activated -->
                        <artifact>*:*</artifact>
                        <excludes>
                            <exclude>META-INF/faces-config.xml</exclude>
                            <exclude>META-INF/*.SF</exclude>
                            <exclude>META-INF/*.DSA</exclude>
                            <exclude>META-INF/*.RSA</exclude>
                        </excludes>
                    </filter>
                </filters>
                <shadedClassifierName>dist</shadedClassifierName>
            </configuration>
        </execution>
    </executions>
</plugin>

The code to start up the container:

EJBContainer ejbContainer = null;
    try {
        final Properties properties = new Properties();
        properties.setProperty(EJBContainer.APP_NAME, applicationName);
        properties.setProperty(EJBContainer.PROVIDER, OpenEjbContainer.class.getName());
        properties.setProperty(OpenEjbContainer.OPENEJB_EMBEDDED_REMOTABLE, "false");
        properties.setProperty("ejbd.disabled", "true");
        properties.setProperty("ejbds.disabled", "true");
        properties.setProperty("admin.disabled", "true");
        properties.setProperty("openejb.jaxrs.application", "false");

        Path launchPath = Paths.get(DemoMain.class.getProtectionDomain().getCodeSource().getLocation().toURI());
        properties.setProperty("openejb.configuration", launchPath.toAbsolutePath() + "/conf/openejb.xml");

        properties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        properties.put("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver");

        // This is the line starting the EJB container
        ejbContainer = EJBContainer.createEJBContainer(properties);
        ejbContainer.getContext().bind("inject", this);

        ejbContainerReady = true;

        final CountDownLatch latch = new CountDownLatch(1);
        // Graceful shutdown
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    logger.info("Shutting down..");
                    latch.countDown();
                    logger.info("Shutdown completed successfully.");
                } catch (final Exception e) {
                    logger.error("Graceful shutdown went wrong. SIGKILL (kill -9) if you want.", e);
                }
            }
        });
        try {
            latch.await();
        } catch (final InterruptedException e) {
            // ignored
        }
    } catch (final Exception e) {
        ejbContainerReady = false;
        throw new RuntimeException(e);
    } finally {
        if (ejbContainer != null) {
            ejbContainer.close();
        }
    }

}

Questions

  • Did I miss something in the configuration of the maven-shade-plugin?

  • How can I build a fat jar using openejb in a standalone manner?

Example Project

UPDATE 1:

I changed the pom according to the answer by P. Merkle. I found an other article here describing the process of shading specifically for TomEE.

pom changed to

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.4.3</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <finalName>demo-shade-${project.version}</finalName>
                <transformers>
                    <transformer
                            implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <manifestEntries>
                            <Main-Class>com.github.rzo1.DemoMain</Main-Class>
                        </manifestEntries>
                    </transformer>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                    <transformer implementation="org.apache.openwebbeans.maven.shade.OpenWebBeansPropertiesTransformer"/>
                </transformers>
                <filters>
                    <filter> <!-- we don't want JSF to be activated -->
                        <artifact>*:*</artifact>
                        <excludes>
                            <exclude>META-INF/faces-config.xml</exclude>
                            <exclude>META-INF/*.SF</exclude>
                            <exclude>META-INF/*.DSA</exclude>
                            <exclude>META-INF/*.RSA</exclude>
                        </excludes>
                    </filter>
                </filters>
                <shadedClassifierName>dist</shadedClassifierName>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.apache.openwebbeans</groupId>
            <artifactId>openwebbeans-maven</artifactId>
            <version>1.7.0</version>
        </dependency>
    </dependencies>
</plugin>

Extecuting this fat jar brings:

INFORMATION - OpenWebBeans Container is starting...
INFORMATION - Adding OpenWebBeansPlugin : [CdiPlugin]
SCHWERWIEGEND - CDI Beans module deployment failed
java.lang.NullPointerException
        at org.apache.openejb.cdi.CdiScanner.handleBda(CdiScanner.java:271)
        at org.apache.openejb.cdi.CdiScanner.init(CdiScanner.java:148)
        at org.apache.openejb.cdi.OpenEJBLifecycle.startApplication(OpenEJBLifecycle.java:179)
        at org.apache.openejb.cdi.ThreadSingletonServiceImpl.initialize(ThreadSingletonServiceImpl.java:189)
        at org.apache.openejb.cdi.CdiBuilder.build(CdiBuilder.java:41)
        at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:913)
        at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:717)
        at org.apache.openejb.OpenEjbContainer$Provider.createEJBContainer(OpenEjbContainer.java:342)
        at javax.ejb.embeddable.EJBContainer.createEJBContainer(EJBContainer.java:56)
        at com.github.rzo1.DemoMain.run(DemoMain.java:90)
        at java.lang.Thread.run(Unknown Source)
INFORMATION - Closing DataSource: demoDS
INFORMATION - Closing DataSource: demoDSNonJTA
Exception in thread "Thread-0" java.lang.RuntimeException: org.apache.openejb.OpenEjbContainer$AssembleApplicationException: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
        at com.github.rzo1.DemoMain.run(DemoMain.java:116)
        at java.lang.Thread.run(Unknown Source)
Caused by: org.apache.openejb.OpenEjbContainer$AssembleApplicationException: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
        at org.apache.openejb.OpenEjbContainer$Provider.createEJBContainer(OpenEjbContainer.java:346)
        at javax.ejb.embeddable.EJBContainer.createEJBContainer(EJBContainer.java:56)
        at com.github.rzo1.DemoMain.run(DemoMain.java:90)
        ... 1 more
Caused by: javax.enterprise.inject.spi.DeploymentException: couldn't start owb context
        at org.apache.openejb.cdi.ThreadSingletonServiceImpl.initialize(ThreadSingletonServiceImpl.java:191)
        at org.apache.openejb.cdi.CdiBuilder.build(CdiBuilder.java:41)
        at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:913)
        at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:717)
        at org.apache.openejb.OpenEjbContainer$Provider.createEJBContainer(OpenEjbContainer.java:342)
        ... 3 more
Caused by: org.apache.openejb.OpenEJBRuntimeException: java.lang.NullPointerException
        at org.apache.openejb.cdi.OpenEJBLifecycle.startApplication(OpenEJBLifecycle.java:200)
        at org.apache.openejb.cdi.ThreadSingletonServiceImpl.initialize(ThreadSingletonServiceImpl.java:189)
        ... 7 more
Caused by: java.lang.NullPointerException
        at org.apache.openejb.cdi.CdiScanner.handleBda(CdiScanner.java:271)
        at org.apache.openejb.cdi.CdiScanner.init(CdiScanner.java:148)
        at org.apache.openejb.cdi.OpenEJBLifecycle.startApplication(OpenEJBLifecycle.java:179)
        ... 8 more

I added a branch with this changes on the GitHub Project for further investigation.

UPDATE 2

I excluded javax.xml.* from the shade:

<excludes>
    <exclude>META-INF/faces-config.xml</exclude>
    <exclude>META-INF/*.SF</exclude>
    <exclude>META-INF/*.DSA</exclude>
    <exclude>META-INF/*.RSA</exclude>
    <exclude>javax/xml/**</exclude>
</excludes> 

However, the exception remains the same as in Update 1. I pushed a related branch to the GitHub repository.

So my question is:

  • What else needs to be excluded from the shade?

With the help of the other answers , I was finally able to find a working solution for building a standalone fat jar, which is working for my use-case.

UPDATE 3:

The steps (for now) are:

  1. Usage of OpenWebBeansPropertiesTransformer instead AppendingTransformer as stated by P. Merkle

  2. Exclude java.xml.* in the shade as stated by Romain Manni-Bucau:

    <excludes>
    <exclude>META-INF/faces-config.xml</exclude>
    <exclude>META-INF/*.SF</exclude>
    <exclude>META-INF/*.DSA</exclude>
    <exclude>META-INF/*.RSA</exclude>
    <exclude>javax/xml/**</exclude>
    

  3. Add a scan.xml in META-INF including only packages / classes, which should be scanned. Current working version can be found here

Question:

  • Is their an official or better way of doing this?
Mazdaism answered 17/2, 2017 at 9:17 Comment(2)
#2708233, blog.eisele.net/2010/12/…Dissatisfy
That's not exactly what I am looking for?!Mazdaism
R
2

http://tomee.apache.org/advanced/shading/index.html and maybe http://tomee.apache.org/advanced/applicationcomposer/index.html are also good starting points.

Now it seems you scan unexpected classes like xml one where the classloader is null. Likely exclude javax.xml.* from scanning or even the shade and it will work

Revivify answered 22/2, 2017 at 7:58 Comment(6)
Why is this then working from IntelliJ IDE? What is the addon in the Jar classpath versus the IDE classpath? What is the property to exclude javax.xml.* from scanning?Mazdaism
ide will use full classpath (vs shade) and jar will be excluded out of thebox, scan.xml can be an option but really think it shouldn't be in the shade too (jre classes shouldnt be embedded in general)Revivify
So what are other types of "unexpected classes" ? Excluding javax.xml.* from the shade does result in the same exceptionMazdaism
I was playing around with the ApplicationComposer but I think this is primarly for testing purpose and not for building a standalone JAR for executing some database operations with JEE services. Do you have any other hints so far? I've added you as a contributor for the GitHub project... feel free to adjust things... the code can then be repackaged, cloned and contributed as an example at the OpenEJB project...Mazdaism
your project works ;). think you just needed to remove hibernate javax api to use tomee onesRevivify
thanks for the commit on the repository. It works indeed now :)Mazdaism
S
1

The exception is caused by merging multiple openwebbeans.properties files from different modules into a single properties file via AppendingTransformer.

This is because openwebbeans.properties files are structured in a special way:

All those files contain a single property configuration.ordinal which defines their 'importance'. Any setting from a property file with a higher configuration.ordinal will overwrite settings from one with a lower configuration.ordinal.

Now, if you naively merge these files--as AppendingTransformer does-- , you will end up with multiple competing ordinal properties in the same file.

The solution is to replace AppendingTransformer with OpenWebBeansPropertiesTransformer, which preserves priorities while merging.

An example pom.xml is available here: http://openwebbeans.apache.org/meecrowave/meecrowave-maven/index.html


Bad news is, however, that this solution uncovers another exception:

java.lang.NullPointerException at org.apache.openejb.cdi.CdiScanner.handleBda(CdiScanner.java:271)

So far I've not been able to identify the cause of that.

Salian answered 21/2, 2017 at 21:31 Comment(1)
Thanks for your investigation and hints. I updated the question and added a related branch on the GitHub project.Mazdaism

© 2022 - 2024 — McMap. All rights reserved.