Getting started with embeddable JBoss EAP 6 / AS 7

EndlosSchleife picture EndlosSchleife · Feb 22, 2013 · Viewed 8.5k times · Source

I'm trying out embeddable JBoss with the EJBContainer.createEJBContainer API described in the spec (JSR 318: Enterprise JavaBeans, Version 3.1, ch. 22: Embeddable Usage), not any of the various predecessors using JBoss specific APIs.

Overview

  • When i invoke my main method normally, a session bean invocation succeeds. But there seem to be some classloading issues, because the JNDI object at "java:jboss/UserTransaction" cannot be cast to javax.transaction.UserTransaction.
  • So i guess the classloading magic of JBoss Modules is required here. With my attempts at that, createEJBContainer does not find any EJBContainerProviders and throws an EJBException.

Note that not all my questions require reading all subsequent details. So if this is too much to read, please have a look at the questions section nevertheless. Thanks!

Environment

JBoss AS 7.1.1.Final (jboss-as-7.1.1.Final.zip with only logging conf changed in standalone.xml)
(Initially JBoss EAP 6.0.1 GA which is the actual target environment - same problem)
Oracle JDK 1.7.0_11
Windows 7 Prof 64 bit

Details

The stateless session bean

package test.helloworld.impl;

import javax.ejb.Remote;
import javax.ejb.Stateless;
import test.helloworld.api.HelloWorld;

@Stateless
@Remote(HelloWorld.class)
public class HelloWorldBean implements HelloWorld
{
    @Override
    public String salute()
    {
        return "Hello, world";
    }
}

... and its business interface:

package test.helloworld.api;

public interface HelloWorld
{
    String salute();
}

The client program

package test.helloworld.client;

import static java.lang.System.out;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import javax.ejb.embeddable.EJBContainer;
import javax.naming.Context;
import javax.transaction.UserTransaction;
import test.helloworld.api.HelloWorld;


public class HelloWorldEmbeddedEjbTestClient
{
    public static void main(String[] args)
    {
        int status = 1;

        try
        {
            main();
            status = 0;
        }
        catch (Throwable e)
        {
            e.printStackTrace(System.err);
            System.err.flush();
        }
        finally
        {
            // Simply returning from main leaves some thread (and
            // hence the JVM) running for another 60s, so force exit
            System.exit(status);
        }
    }


    private static void main() throws Exception
    {
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(EJBContainer.MODULES, new File[]{new File("./HelloWorld-EJB.jar")});
        EJBContainer container = EJBContainer.createEJBContainer(properties);

        try
        {
            Context jndiContext = container.getContext();
            Object serviceObj = jndiContext.lookup("java:global/HelloWorld-EJB/HelloWorldBean");
            out.println("service:\t" + serviceObj);
            HelloWorld service = (HelloWorld) serviceObj;
            out.println("result:\t" + service.salute());

            callInTx(service, jndiContext);
        }
        finally
        {
            out.println("closing EJBContainer...");
            container.close();
            out.println("EJBContainer closed.");
        }
    }


    private static void callInTx(HelloWorld service, Context jndiContext) throws Exception
    {
        UserTransaction tx = (UserTransaction) jndiContext.lookup("java:jboss/UserTransaction");
        tx.begin();
        out.println("result in tx:\t" + service.salute());
        tx.commit();
    }
}

Packaging

C:\eclipse\projects\HelloWorldSlSB-Client\rt\HelloWorld-API.jar:

META-INF/MANIFEST.MF
test/helloworld/api/HelloWorld.class

C:\eclipse\projects\HelloWorldSlSB-Client\rt\HelloWorld-EJB.jar:

META-INF/MANIFEST.MF
META-INF/jboss-deployment-structure.xml
test/helloworld/impl/HelloWorldBean.class
test/helloworld/api/HelloWorld.class

HelloWorld-EJB.jar:META-INF/jboss-deployment-structure.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<jboss-deployment-structure>
    <deployment>
        <dependencies></dependencies>
        <exclusions>
            <module name="Classpath"/>
        </exclusions>
    </deployment>
</jboss-deployment-structure>

C:\eclipse\projects\HelloWorldSlSB-Client\rt\modules\test\helloworld\client\main\HelloWorldSlSB-Client.jar:

META-INF/MANIFEST.MF
test/helloworld/client/HelloWorldEmbeddedEjbTestClient.class

C:\eclipse\projects\HelloWorldSlSB-Client\rt\modules\test\helloworld\client\main\module.xml:

    <main-class name="test.helloworld.client.HelloWorldEmbeddedEjbTestClient"/>

    <resources>
        <resource-root path="HelloWorldSlSB-Client.jar"/>
    </resources>

    <dependencies>
        <module name="javax.api"/>
        <module name="javax.ejb.api"/>
        <module name="org.jboss.as.embedded" export="true"/>
<!--
        <module name="org.jboss.as.server" export="true"/>
-->
    </dependencies>
</module>

All META-INF/MANIFEST.MF files contain nothing but "Manifest-Version: 1.0".

Results

Direct invocation

With a direct invocation of HelloWorldEmbeddedEjbTestClient.main with -Xmx512m -XX:MaxPermSize=256m, classpath

C:\eclipse\output\HelloWorldSlSB-Client
C:\java\jboss-as-7\jboss-modules.jar
C:\java\jboss-as-7\modules\org\jboss\as\embedded\main\jboss-as-embedded-7.1.1.Final.jar
C:\java\jboss-as-7\modules\javax\ejb\api\main\jboss-ejb-api_3.1_spec-1.0.1.Final.jar
C:\java\jboss-as-7\modules\javax\transaction\api\main\jboss-transaction-api_1.1_spec-1.0.0.Final.jar
C:\java\jboss-as-7\modules\org\jboss\logging\main\jboss-logging-3.1.0.GA.jar
C:\java\jboss-as-7\modules\org\jboss\as\controller-client\main\jboss-as-controller-client-7.1.1.Final.jar
C:\java\jboss-as-7\modules\org\jboss\logmanager\main\jboss-logmanager-1.2.2.GA.jar
C:\java\jboss-as-7\modules\org\jboss\dmr\main\jboss-dmr-1.1.1.Final.jar
C:\eclipse\projects\HelloWorldSlSB-Client\rt\HelloWorld-API.jar

and system properties

-Duser.language=en
-Djboss.home=c:/java/jboss-as-7
-Djboss.home.dir=c:/java/jboss-as-7
-Dorg.jboss.as.embedded.ejb3.BARREN=true
-Dfile.encoding=ISO-8859-1

the EJB invocation succeeds, but JNDI object "java:jboss/UserTransaction" cannot be cast to javax.transaction.UserTransaction:

...
19:21:01,875 INFO  [org.jboss.ejb.client] (main) JBoss EJB Client version 1.0.5.Final
service:    Proxy for remote EJB StatelessEJBLocator{appName='', moduleName='HelloWorld-EJB', distinctName='', beanName='HelloWorldBean', view='interface test.helloworld.api.HelloWorld'}
result: Hello, world
closing EJBContainer...
19:21:06,362 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-7) JBAS015877: Stopped deployment HelloWorld-EJB.jar in 90ms
19:21:06,370 INFO  [org.jboss.as.repository] (pool-9-thread-1) JBAS014901: Content removed from location c:\java\jboss-as-7\standalone\data\content\35\424415b9a67d64fe8a6dc7ee0700480282f34b\content
19:21:06,370 INFO  [org.jboss.as.server] (pool-9-thread-1) JBAS018558: Undeployed "HelloWorld-EJB.jar"
19:21:06,390 INFO  [org.jboss.as.osgi] (MSC service thread 1-1) JBAS011942: Stopping OSGi Framework
EJBContainer closed.
java.lang.ClassCastException: org.jboss.tm.usertx.client.ServerVMClientUserTransaction cannot be cast to javax.transaction.UserTransaction
    at test.helloworld.client.HelloWorldEmbeddedEjbTestClient.callInTx(HelloWorldEmbeddedEjbTestClient.java:65)
    at test.helloworld.client.HelloWorldEmbeddedEjbTestClient.main(HelloWorldEmbeddedEjbTestClient.java:52)
    at test.helloworld.client.HelloWorldEmbeddedEjbTestClient.main(HelloWorldEmbeddedEjbTestClient.java:21)

(Looks a little strange, because the exception is caught and printed at the end of main. But of course, it happens before the container is closed.)

The debugger shows that the JNDI object's getClass().getClassLoader() is org.jboss.modules.ModuleClassLoader
ModuleClassLoader for Module "org.jboss.jboss-transaction-spi:main" from local module loader @4b436982 (roots: c:\java\jboss-as-7\modules)
and its getClass().getInterfaces()[0] /* == interface javax.transaction.UserTransaction */.getClassLoader() is
ModuleClassLoader for Module "javax.transaction.api:main" from local module loader @4b436982 (roots: c:\java\jboss-as-7\modules)
However, UserTransaction.class.getClassLoader() is of type sun.misc.Launcher$AppClassLoader.

jboss-modules.jar invocation

Invoked via org.jboss.modules.Main.main (i.e. what java -jar jboss-modules.jar also does) with classpath

C:\java\jboss-as-7\jboss-modules.jar

system properties as above, and arguments

-mp "C:\java\jboss-as-7\modules;C:\eclipse\projects\HelloWorldSlSB-Client\rt\modules"
test.helloworld.client

it fails already in createEJBContainer:

javax.ejb.EJBException: Unable to instantiate container with factories []
    at javax.ejb.embeddable.EJBContainer.createEJBContainer(EJBContainer.java:97)
    at test.helloworld.client.HelloWorldEmbeddedEjbTestClient.main(HelloWorldEmbeddedEjbTestClient.java:42)
    at test.helloworld.client.HelloWorldEmbeddedEjbTestClient.main(HelloWorldEmbeddedEjbTestClient.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.jboss.modules.Module.run(Module.java:260)
    at org.jboss.modules.Main.main(Main.java:291)

Debugging shows that this happens because in method EJBContainer.findAllFactories(), no resources "META-INF/services/javax.ejb.spi.EJBContainerProvider" are found in the thread context classloader, which is
ModuleClassLoader for Module "test.helloworld.client:main" from local module loader @1afec586 (roots: C:\java\jboss-as-7\modules,C:\eclipse\projects\HelloWorldSlSB-Client\rt\modules)

Questions

  • Does anyone know how to fix this or has gotten anything similar to work?
  • Is a normal JBoss installation the right starting point, or do i need a specific embeddable variant?
  • Is UserTransaction even supposed to be usable in the embeddable scenario?
    • If it is, does that mean i could also use one XA datasource for both JPA 2 CMT entities and legacy JDBC code? And both could participate in the same transaction (e.g. JDBC code starts tx with java.sql.Connection.setAutoCommit(false), then calls EJB with TransactionAttributeType.REQUIRED)?
  • Can someone point me to some documentation? All i find is about the older stuff with JBoss specific API, not the standard EJB 3.1 way.
    • Preferably, documentation how to use without any test frameworks, since i'm evaluating this for production use.

Thanks for reading all (or part of :-) this!

Answer

EndlosSchleife picture EndlosSchleife · Feb 25, 2013

I found the solution for the JBoss Modules way, after remembering that i had read that META-INF/services need to be imported explicitly. So, the client's module.xml needs a services="import" and now looks like this:

C:\eclipse\projects\HelloWorldSlSB-Client\rt\modules\test\helloworld\client\main\module.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<module xmlns="urn:jboss:module:1.1" name="test.helloworld.client">
    <main-class name="test.helloworld.client.HelloWorldEmbeddedEjbTestClient"/>

    <resources>
        <resource-root path="HelloWorldSlSB-Client.jar"/>
    </resources>

    <dependencies>
        <module name="javax.api"/>
        <module name="javax.ejb.api"/>
        <module name="org.jboss.as.embedded" services="import"/>
        <module name="org.jboss.logmanager"/>
        <module name="test.helloworld"/>
    </dependencies>
</module>

Besides, an error message told me to set system property java.util.logging.manager to org.jboss.logmanager.LogManager, so i added a corresponding -D VM argument. Then came ClassNotFoundException: org.jboss.logmanager.LogManager, so i added the logmanager dependency above.

Only putting the EJB jar into the createEJBContainer Map argument was not sufficient for the client to see it, so it seems the EJB must also be a module which the client can explicitely depend on (<module name="test.helloworld"/> above). So i moved HelloWorld-API.jar and HelloWorld-EJB.jar to directory modulePath/test/helloworld/main/ (i.e. C:\eclipse\projects\HelloWorldSlSB-Client\rt\modules\test\helloworld\main), changed the path in new File("./HelloWorld-EJB.jar") accordingly, and added a module.xml for them:

C:\eclipse\projects\HelloWorldSlSB-Client\rt\modules\test\helloworld\main\module.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<module xmlns="urn:jboss:module:1.1" name="test.helloworld">
    <resources>
        <resource-root path="HelloWorld-API.jar"/>
        <resource-root path="HelloWorld-EJB.jar"/>
    </resources>
</module>

I removed test/helloworld/api/HelloWorld.class from the EJB jar. Not necessary, but maybe cleaner.

Edit 2013-02-27: Found a simpler working invocation, still using org.jboss.modules.Main, but with the EJB deployed as we'll probably do for the non-embedded use and no module.xml files required. And the no-arg createEJBContainer() is now sufficient, as i included the EJB jar in the -cp argument instead.

%JBOSS_HOME%\standalone\deployments contains

HelloWorld-API.jar
HelloWorld-EJB.jar

HelloWorld-EJB.jar:

META-INF/MANIFEST.MF
META-INF/jboss-deployment-structure.xml
test/helloworld/impl/HelloWorldBean.class

HelloWorld-EJB.jar:META-INF/jboss-deployment-structure.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="deployment.HelloWorld-API.jar"/>
        </dependencies>
        <exclusions>
            <module name="Classpath"/>
        </exclusions>
    </deployment>
</jboss-deployment-structure>

Invocation:

C:\java\jdk1.7\bin\java -Xmx512m -XX:MaxPermSize=256m -Duser.language=en
-Djboss.home=c:/java/jboss-as-7 -Djboss.home.dir=c:/java/jboss-as-7
-Djava.util.logging.manager=org.jboss.logmanager.LogManager
-classpath "C:\java\jboss-as-7\jboss-modules.jar"
org.jboss.modules.Main
-mp "C:\java\jboss-as-7\modules"
-dep "javax.ejb.api, org.jboss.as.embedded, org.jboss.logmanager"
-cp "C:\eclipse\output\HelloWorldSlSB-Client;C:\java\jboss-as-7\standalone\deployments\HelloWorld-API.jar;C:\java\jboss-as-7\standalone\deployments\HelloWorld-EJB.jar"
test.helloworld.client.HelloWorldEmbeddedEjbTestClient

Directory C:\eclipse\projects\HelloWorldSlSB-Client\rt\modules: empty, no longer used.

End of edit 2013-02-27

Remaining questions

  • If someone knows how to get the direct invocation without org.jboss.modules.Main running, i'd still like to know.
  • Documentation links are still very welcome.
  • The UserTransaction / JDBC question

BTW, maybe someone with enough reputation could add a tag like jboss-modules?