Sunday, 21 June 2009

Tomcat - load & bind an external naming context

In my previous post Injecting JBoss EJB3 beans into Tomcat6, I used Tomcat's ability to bind EJB's to the local JNDI context by specifying JBoss' NamingContextFactory and the URL to the EJB. However in the process of using it, I discovered a problem with Stateful Session Beans.

I was getting NoSuchEJBException - either after a redeploy to JBoss, or calling the @Remove annotated method on the session bean. A lot of debugging later, I discovered that the cause was that Tomcat was initially storing a ejb ref (org.apache.EjbRef) in the context and when first accessed it looks up the EJB using the factory and then replaces the reference in the context with the instance of the EJB. Every subsequent lookup in the context returns the same instance.

This however breaks the JEE5 contract:
FROM JSR 220: NOTE: When a stateful session bean is looked up or otherwise
obtained through the explicit JNDI lookup mechanisms, the container must
provide a new stateful session bean instance, as required by the Java EE
specification (Section “Java Naming and Directory Interface (JNDI) Naming
Context” [12]).
I initially tried a few hacks to stop Tomcat 6 storing the instance and only keeping the ref, but I was worried about the lag every I required a new instance. Each time it creates a context factory, EJB factory etc to look up the EJB. If you were manually doing the lookup using a custom service locator, you would create a singleton service locator and stores a initial context with the remote app server's details pumped in. Thus everytime you wanted an instance a single call to the existing context would be all that was required.

My solution I felt was to bind an external context naming tree to the local naming tree. That way you could reference any external object easily on the external context with just a @EJB ref. The resulting patch is located here, it is against Tomcat 6.0.20. You can also download my precompiled binary:
catalina.jar
And the source:
apache-tomcat-6.0.20-src

To use it create a context.xml:
<Context>
   <!-- Binds the remote context tree starting at myear to local name java:/comp/env/ejb. -->
   <Resource name="ejb" type="javax.naming.Context" factory="org.jnp.interfaces.NamingContextFactory" URL="jnp://apphost:1099/myear"/>
</Context>
Next if you want to reference a bean which has a @Stateful(mappedName="myear/myBean"), using the following in your JSF/contains managed bean:
public class TomcatManagedBean
{
   @EJB(name="ejb/myBean")
   private MyBeanRemote session;

2 comments:

  1. I tried that but for WL 10 but when application starts only says:

    9/10/2009 10:49:48 AM org.apache.catalina.core.NamingContextListener addResource
    ADVERTENCIA: Cannot find message associated with key naming.jmxRegistrationFailed

    Some idea?

    I have wlfullclient.jar in Tomcat's lib and the context is (without < and > symbos)
    Resource name="remote" type="javax.naming.Context"
    factory="weblogic.jndi.WLInitialContextFactory" URL="t3://weblogicw32:7001" /

    I already got the EJB I want from WL server with test class using wlfullclient.jar

    Thanks.

    ReplyDelete
  2. I've uploaded a new build of my catalina.jar (same URL) to output some more messages.

    Try downloading the new version and see what's causing the errors. I usually get messages like 'mypath' not bound.

    Also for the url you probably need to make it more than the entire server. E.g. have all the ejb's mapped prefix by 'someapp' so your url would be:
    t3://weblogicw32:7001/someapp
    Or if its spread over multiple subpaths just map each one.

    ReplyDelete