Thursday, 18 June 2009

Injecting JBoss EJB3 beans into Tomcat6

Injecting EJB's into a managed bean (a bean spawned by appserver, of a JSF managed bean) inside an application server is simple:
public class AppManagedBean
{
   @EJB(mappedName="ejb/myBean")
   private MyBeanRemote session;
The container looks up the mapped bean and injects it. Easy.

However in tomcat it gets a little messy. Tomcat doesn't document how to inject them. It assumes something annotated with @EJB is in the context somewhere and tries to look it up.
It has documented how to lookup ejb's by defining entries in your context.xml or server.xml. But there isn't anything documented for defining EJB's. Looking at the soure it has support for <Ejb/> references but no real clue on how to configure it. So after stepping through the code I came up with the following soloution:

My environment is JBoss 5 running remotely (on localhost). A seperate instance of Tomcat 6. In it I have defined a session bean:
@Stateful(mappedName="userSession")
public class UserSessionBean implements UserSessionRemote 
This bean is used by a JSF backing bean (i.e. a managed bean):
public class TomcatManagedBean
{
   @EJB(name="ejb/myBean")
   private MyBeanRemote session;
In order for this bean to bound to the context, you need to configure Tomcat. There are 2 ways: in the server.xml or in the context.xml
I find that putting it in META-INF/context.xml is the easiest, as it gets reloaded nicely in Eclipse. The name="ejb/myBean" corresponds to the name attribute in the <Ejb/> below. In the URL is the appserver name, port, then the mapped name (it could be myProject/ejb/bean or whatever you feel like) - as long as it matched the mappedName on the EJB.
<Context>
   <!-- Makes Tomcat manage the ejb for you. -->
   <Ejb name="ejb/myBean" type="Session" factory="org.jnp.interfaces.NamingContextFactory" URL="jnp://localhost:1099/userSession" />
</Context>
Update 2010/02/02: Correct the mappedName in AppManagedBean

9 comments:

  1. Hi, I am currently trying to do the same thing like you. I have a Webapp runing on Tomcat 6.0.20 and an EJB 3.0 on JBoss 4.3.
    But it is absolutly not workling. I am trying to inject my EJB in Tomcat but it doesn't work.

    If you like to help me, and send me your email-adress to gfemajor2[at]gmx.de i can send you my 2 test projects. (I will not post my hole code here, because i think it is to mutch for a comment ;-)). So that you can have a look at it and tell me what am i doing wrong.



    Thank you so far

    Christian

    ReplyDelete
  2. I just noticed I had a mapped name slightly wrong above which I've corrected - sorry if that caused some confusion.

    I've also got an alternate way of mapping it (in which I've had to edit the tomcat source). Tomcat - load & bind an external naming context

    I'll drop you an email.

    ReplyDelete
  3. Hi Chris,
    thank you for your article, it contains the information I am looking for, but I have a problem left.
    As soon as I am accessing the injected EJB I get an NullPointerException, I think it is because I don't import the right libraries.
    This are the libraries I import, it would be nice, if you could take a look, if they are complete:
    groupId: javax.ejb
    artefactId: ejb-api
    version: 3.0
    and
    groupId:javax.persistence
    artefactId: persistence-api
    version: 1.0

    All the best,
    Sebastian

    ReplyDelete
  4. I've just added a new post describing how I build my client jars. I'm assuming your using it in a webapp (like tomcat) outside of the app server (e.g. jboss)?

    If your inside the appserver you don't need to include the persistence-api or ejb-api.
    And for outside the appserver but inside a web server you would have all those jars inside the server's shared directory (e.g. tomcat/lib). Just make the dependencies <scope>provided</scope>

    ReplyDelete
  5. Hi Chris,
    thank you for your help :)
    your right the frontend is running in a ServletContainer (Tomcat 6.0.18) and the backend in an ApplicationServer (JBoss 5.1.0.GA).
    I changed the libraries accoding to your description (I put the client jars into the lib directory of the Tomcat and I made the dependencies provided), but it won't work.
    Now I am thinking, that there might be something missing in my web.xml? It just contains the follwoing part:
    <servlet>
    <display-name>FacesServlet</display-name>
    <servlet-name>FacesServlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>FacesServlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
    </servlet-mapping>
    Do you know if I need something more?

    ReplyDelete
  6. No, I was wrong it truly worked :) *Thank you very much* :)
    But I have one Exception left:
    Caused by: java.lang.IllegalArgumentException: Can not set com.backend.service.UserServiceRemote field com.frontend.action.LoginBean.userService to org.jnp.interfaces.NamingContext
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:146)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:150)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:63)
    at java.lang.reflect.Field.set(Field.java:657)
    at org.apache.catalina.util.DefaultAnnotationProcessor.lookupFieldResource(DefaultAnnotationProcessor.java:213)
    at org.apache.catalina.util.DefaultAnnotationProcessor.processAnnotations(DefaultAnnotationProcessor.java:142)
    at com.sun.faces.vendor.Tomcat6InjectionProvider.inject(Tomcat6InjectionProvider.java:82)
    ... 38 more

    Do you have a clue?

    ReplyDelete
  7. Hi Chris :)

    I could fix the last problem :) Now it is working! (I fixed it by removing the attribute "mappedName" in the SessionBean) Thanks again.

    All the best,
    Sebastian

    ReplyDelete
  8. Hi Sebastian,

    No worries.
    I was going to say that the problem you having was probably related to what it was trying to resolve actually resolved to a directory not the bean.

    Looking at my example its slightly wrong:
    It should be @EJB(mappedName="ejb/myBean")
    That matches the line in context.xml name="ejb/myBean". But removing the mapped name causes it to look up by class interface which is why it works.

    -Chris

    ReplyDelete