Wednesday, March 10, 2010

OpenEjb, Jetty and Maven - Transaction Management

I've been meaning to blog about getting transaction management working with OpenEjb and Jetty using jetty:run... it's still an on-going story... but the following might get you going...

First off, in your pom.xml you need to add the configuration for maven-jetty-plugin... we need to dance around the various activemq/activeio versions and ensure that we get the correct version of ant...

<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>org.apache.openejb.examples</groupId>
    <artifactId>jetty-openejb</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>jetty-openejb Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <version>6.1.22</version>

                <dependencies>
                    <dependency>
                        <groupId>org.apache.activemq</groupId>
                        <artifactId>activemq-core</artifactId>
                        <version>4.1.1</version>
                        <exclusions>
                            <exclusion>
                                <groupId>commons-logging</groupId>
                                <artifactId>commons-logging</artifactId>
                            </exclusion>
                            <exclusion>
                                <groupId>commons-logging</groupId>
                                <artifactId>commons-logging-api</artifactId>
                            </exclusion>
                            <exclusion>
                                <groupId>org.apache.activemq</groupId>
                                <artifactId>activeio-core</artifactId>
                            </exclusion>
                        </exclusions>
                    </dependency>

                    <dependency>
                        <groupId>org.apache.activemq</groupId>
                        <artifactId>activemq-ra</artifactId>
                        <version>4.1.1</version>
                        <exclusions>
                            <exclusion>
                                <groupId>commons-logging</groupId>
                                <artifactId>commons-logging</artifactId>
                            </exclusion>
                            <exclusion>
                                <groupId>commons-logging</groupId>
                                <artifactId>commons-logging-api</artifactId>
                            </exclusion>
                            <exclusion>
                                <groupId>org.apache.activemq</groupId>
                                <artifactId>activeio-core</artifactId>
                            </exclusion>
                        </exclusions>
                    </dependency>

                    <dependency>
                        <groupId>org.apache.activemq</groupId>
                        <artifactId>activeio-core</artifactId>
                        <version>3.1.2</version>
                        <exclusions>
                            <exclusion>
                                <groupId>commons-logging</groupId>
                                <artifactId>commons-logging</artifactId>
                            </exclusion>
                            <exclusion>
                                <groupId>commons-logging</groupId>
                                <artifactId>commons-logging-api</artifactId>
                            </exclusion>
                        </exclusions>
                    </dependency>

                    <dependency>
                        <groupId>org.apache.openejb</groupId>
                        <artifactId>openejb-core</artifactId>
                        <version>3.1.2</version>
                        <exclusions>
                            <exclusion>
                                <groupId>org.apache.activemq</groupId>
                                <artifactId>activemq-core</artifactId>
                            </exclusion>
                            <exclusion>
                                <groupId>org.apache.activemq</groupId>
                                <artifactId>activemq-ra</artifactId>
                            </exclusion>
                            <exclusion>
                                <groupId>org.apache.activemq</groupId>
                                <artifactId>activeio-core</artifactId>
                            </exclusion>
                            <exclusion>
                                <groupId>junit</groupId>
                                <artifactId>junit</artifactId>
                            </exclusion>
                        </exclusions>
                    </dependency>
                    <!-- in order to use the latest version of openejb, we need to exclude
                         the dependencies provided in jsp-2.1-jetty -->
                    <dependency>
                        <groupId>org.mortbay.jetty</groupId>
                        <artifactId>jsp-2.1-jetty</artifactId>
                        <version>6.1.22</version>
                        <exclusions>
                            <exclusion>
                                <groupId>ant</groupId>
                                <artifactId>ant</artifactId>
                            </exclusion>
                        </exclusions>
                    </dependency>

                </dependencies>
                <configuration>
                    <jettyConfig>${basedir}/src/main/jetty/jetty.xml</jettyConfig>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Next we need to configure a src/main/jetty/jetty.xml to bind the UserTransaction instance into jetty...

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
<Configure id="srv" class="org.mortbay.jetty.Server">

    <New class="javax.naming.InitialContext">
        <Arg>
            <New class="java.util.Properties">
                <Call name="setProperty">
                    <Arg>java.naming.factory.initial</Arg>
                    <Arg>org.apache.openejb.client.LocalInitialContextFactory</Arg>
                </Call>
            </New>
        </Arg>
        <Call name="lookup" id="tm">
            <Arg>openejb:TransactionManager</Arg>
        </Call>
    </New>

    <New class="org.mortbay.jetty.plus.naming.Transaction">
        <Arg>
            <New class="org.apache.openejb.core.CoreUserTransaction">
                <Arg>
                    <Ref id="tm"/>
                </Arg>
            </New>
        </Arg>
    </New>

</Configure>

And presto-chango, now jetty has a transaction manager provided by openejb.  (Note: if we don't mind storing that in a jetty-env in /WEB-INF, you can put the same config in WEB-INF/jetty-env.xml)

OK, so here are the issues:
  • Reloading does not work (because org.apache.openejb.core.ivm.naming.IvmContext does not support the destroySubcontext(Context) method
  • We are using jetty's JNDI provider in the web-app and openejb's JNDI provider for the EJBs... this is because

    When jetty binds names to JNDI (using org.mortbay.jetty.plus.naming.Resource or org.mortbay.jetty.plus.naming.Transaction) it binds the object to JNDIName and it also binds a NamingEnrtry for the object to __/JNDIName

    Unfortunately, openejb's JNDI implementation seems to be somewhat strange in this regard... if we add the SystemProperties to jetty to have it use openejb's JNDI implementation, e.g. add the following to /project/build/plugins/plugin[maven-jetty-plugin]/configuration/systemProperties

                            <systemProperty>
                                <name>java.naming.factory.initial</name>
                                <value>org.apache.openejb.client.LocalInitialContextFactory</value>
                            </systemProperty>


    Then when we bind __/UserTransaction it gets bound to openejb:__/UserTransaction but when we lookup __/UserTransaction openejb looks up openejb:local/__/UserTransaction

    And that is just for starters... there seems to be a whole host of other JNDI strangeness between jetty's side and openejb's side

    The side effect of all this is that if you want resource refs to work correctly, you need to fish them out of openejb's JNDI context and push them into jetty's JNDI context
In any case this is at least a start!

10 comments:

Josh Peters said...

Very cool. I've been trying to figure out this setup for a few weeks; you've really saved me some time.

bsbodden said...

Nice work Stephen. Do you have a similar POM for the OpenEJB Tomcat integration?

Stephen Connolly said...

I haven't tried the Tomcat integration, but I believe it's easier. I'm currently looking into forking jetty-plus to avaoid the requirement for jetty to have its own jndi implementation at all

cordenier said...

Hi

Thanks for this article, i have succeeded to run openejb and jetty using you pom.xml configuration.

But where do we put ejb descriptors, i have added a EJB jar file in the classpath but cannot see it loaded, any idea ?

Stephen Connolly said...

@cordenier

It all depends!

The way I do this is I add the ejb dependencies as dependencies to the jetty plugin, so that they are available on the openejb classpath... you are treating the jetty-maven-plugin like it is the EAR in which your application is deployed. You'll then need to fish out the ejb refs from openejb's context and push them into the jetty context using jetty.xml

cordenier said...

@Stephen Thanks for quick reply !

ok now i got you, my EJB is now deployed but i cannot access to it but i guess i have enough informations now to find out why.

Thanks, you saved me some time

Stephen Connolly said...

@cordenier

What you need to do is add additional Call entries to the end of the New section where we create the openejb initial context, each one looking up your EJBs

Then you add additional Resource entries to your jetty.xml to bind these back into jetty's jndi tree

cordenier said...

@Stephen

Actually, i have a resource locator layer that implements openejb DeploymentListener, and EJB ids are located via their class name.

There is a weird thing, the locator map is well populated but when i ask for an EJB ref with a class as parameter it does not return the value associated to the class. I guess i have to solve a classloading issue first.

Btw, Thank you for your help.

cordenier said...
This comment has been removed by the author.
cordenier said...

Now it works but i had to set ejb lib to provided scope in my web project to avoid classloading issues.

By the way, everything looks to magic now :) Do you know Which class is responsible for auto launching OpenEJB, because i don't see any other configuration than the JNDI one...