dimanche 25 août 2013

EJB 2 on Jboss 7.1 example

I recently have add the opportunity to migrate an historical JEE web application from JBoss 4 to JBoss 7.1. One of the more complicated point I faced was the migration of the EJB2 components. The old xml configuration was not correct anymore and it was very difficult to find documentation, tutorials or concrete examples, even on JBoss forums. Of course this is understandable because this old version of EJB is not used anymore (hopefully!), but it still exists in several historical web applications.

The goal of this new thread is not to explain all the EJB 2 mechanisms but to share what I really missed during the migration, that is a working example of EJB2 sessions and EJB2 entities on JBoss 7.1. Since JEE 6, it is possible to package the EJB directly in a war archive, so this example will be based on that.

The source code is available here.

EJB 2 session java code

I will create an EJB 2 session called HelloWorld with a simple method returning a String object. Three classes must be created to do that :

  • The EJB interface
package com.jsebfranck.jboss.ejb2.session;

import java.rmi.RemoteException;
import javax.ejb.EJBObject;

public interface HelloWorldEJB extends EJBObject {
 public String helloWorld() throws RemoteException;
}

  • The implementation
package com.jsebfranck.jboss.ejb2.session;

import java.rmi.RemoteException;
import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

public class HelloWorldEJBBean implements SessionBean {
 private static final long serialVersionUID = 1L;

 public String helloWorld() throws RemoteException {
  return "Hello world, I am an EJB2 session";
 }
 
 public void ejbActivate() throws EJBException, RemoteException {}
 public void ejbPassivate() throws EJBException, RemoteException {}
 public void ejbRemove() throws EJBException, RemoteException {}
 public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException {}
}

  • And the EJB Home
package com.jsebfranck.jboss.ejb2.session;

import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface HelloWorldEJBHome extends EJBHome {
 public HelloWorldEJB create() throws RemoteException, CreateException;
}

EJB 2 entity java code

For the EJB 2 entity example, I will create an object called Member with two fields : an id and a login. Three java objects are also needed to do that :

  • The EJBLocalObject
package com.jsebfranck.jboss.ejb2.entity;

import javax.ejb.EJBLocalObject;

public interface Member extends EJBLocalObject {
    public Long getId();
    public String getLogin();
}
  • The EntityBean
package com.jsebfranck.jboss.ejb2.entity;

import javax.ejb.CreateException;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;

public abstract class MemberBean implements EntityBean {

  private static final long serialVersionUID = 1L;
  private transient EntityContext ctx;

  public MemberBean() {
  }

  public Long ejbCreate(Long id, String login) throws CreateException {
    setId(id);
    setLogin(login);
    return id;
  }

  public void ejbPostCreate(Long id, String login) {
  }

  public abstract Long getId();
  public abstract void setId(Long id);

  public abstract String getLogin();
  public abstract void setLogin(String login);

  public void setEntityContext(EntityContext ctx) {
    this.ctx = ctx;
  }

  public void unsetEntityContext() {
    this.ctx = null;
  }

  public void ejbActivate() {}
  public void ejbPassivate() {}
  public void ejbLoad() {}
  public void ejbStore() {}
  public void ejbRemove() {}
}

  • And the EJBHome
package com.jsebfranck.jboss.ejb2.session;

import java.rmi.RemoteException;

import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface HelloWorldEJBHome extends EJBHome {
 public HelloWorldEJB create() throws RemoteException, CreateException;
}

XML configuration


Now we have to declare the EJB entity and the EJB session in the xml configuration. A first file is needed for that, ejb-jar.xml. This file must be put in the /WEB-INF folder of the war archive.

<ejb-jar version="3.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://java.sun.com/xml/ns/javaee" 
xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd">
  <enterprise-beans>
    <session>
      <ejb-name>HelloWorldEJB</ejb-name>
      <home>com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome</home>
      <remote>com.jsebfranck.jboss.ejb2.session.HelloWorldEJB</remote>
      <ejb-class>com.jsebfranck.jboss.ejb2.session.HelloWorldEJBBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Container</transaction-type>
    </session>
    <entity>
      <ejb-name>MemberEJB</ejb-name>
      <local-home>com.jsebfranck.jboss.ejb2.entity.MemberHome</local-home>
      <local>com.jsebfranck.jboss.ejb2.entity.Member</local>
      <ejb-class>com.jsebfranck.jboss.ejb2.entity.MemberBean</ejb-class>
      <persistence-type>Container</persistence-type>
      <prim-key-class>java.lang.Long</prim-key-class>
      <reentrant>false</reentrant>
      <cmp-version>2.x</cmp-version>
      <abstract-schema-name>member</abstract-schema-name>
      <cmp-field>
        <field-name>id</field-name>
      </cmp-field>      
      <cmp-field>
        <field-name>login</field-name>
      </cmp-field>
      <primkey-field>id</primkey-field>
    </entity>
  </enterprise-beans>
</ejb-jar>

Now let's configure the mapping of the EJB entity. This is done in the jbosscmp-jdbc.xml file which must be put in the META-INF folder of the war archive.

<jbosscmp-jdbc>
  <defaults>
    <datasource>java:jboss/datasources/hsqldbDS</datasource>
  </defaults>
  <enterprise-beans>
    <entity>
      <ejb-name>MemberEJB</ejb-name>
      <row-locking>false</row-locking>
      <table-name>simple</table-name>
      <cmp-field>
        <field-name>id</field-name>
        <column-name>id</column-name>
      </cmp-field>      
      <cmp-field>
        <field-name>login</field-name>
        <column-name>login</column-name>
      </cmp-field>
    </entity>
  </enterprise-beans>
</jbosscmp-jdbc>


JBoss configuration

As you noticed in the jbosscmp-jdbc.xml file, a datasource is required for the entity. This datasource is configured directly in the jboss configuration. As I launch JBoss 7.1 in a standalone mode, I put the following configuration in the $JBOSS_HOME/standalone/configuration/standalone-full.xml file.

In this example, a use a hsql database :

<subsystem xmlns="urn:jboss:domain:datasources:1.0"> <datasources> <datasource enabled="true" jndi-name="java:jboss/datasources/hsqldbDS" jta="true" pool-name="hsqldbDS" use-ccm="true" use-java-context="true"> <connection-url>jdbc:hsqldb:file:/Users/jsebfranck/Documents/database/standaloneHsqldb</connection-url> <driver>hsqldb</driver> <pool> <prefill>false</prefill> <use-strict-min>false</use-strict-min> <flush-strategy>FailingConnectionOnly</flush-strategy> </pool> <security> <user-name>sa</user-name> </security> </datasource>


Deployment in jboss

When you deploy the web application on jboss (standalone.sh --server-config=standalone-full.xml), you can see the JNDI names of both EJB in the logs. This will help us to do our lookups in the client.


19:07:56,689 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-2) JBAS015876: Starting deployment of "WarWithEJB2.war"
19:07:57,069 INFO  [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-4) JNDI bindings for session bean named MemberEJB in deployment unit deployment "WarWithEJB2.war" are as follows:

 java:global/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.Member
 java:app/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.Member
 java:module/MemberEJB!com.jsebfranck.jboss.ejb2.entity.Member
 java:global/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome
 java:app/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome
 java:module/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome

19:07:57,073 INFO  [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-4) JNDI bindings for session bean named HelloWorldEJB in deployment unit deployment "WarWithEJB2.war" are as follows:

 java:global/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJB
 java:app/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJB
 java:module/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJB
 java:jboss/exported/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJB
 java:global/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome
 java:app/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome
 java:module/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome
 java:jboss/exported/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome

Simple servlet client

We are now able to test our EJBs. The following code calls the helloWorld method of the EJB session, then it creates a new line in the Member table.

package com.jsebfranck.jboss.servlet;

import java.io.IOException;

import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.jsebfranck.jboss.ejb2.entity.Member;
import com.jsebfranck.jboss.ejb2.entity.MemberHome;
import com.jsebfranck.jboss.ejb2.session.HelloWorldEJB;
import com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome;

@WebServlet("/Ejb2Servlet")
public class Ejb2Servlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    try {
      // EJB 2 session
      HelloWorldEJBHome helloWorldEJBHome = (HelloWorldEJBHome) new InitialContext().lookup("java:global/WarWithEJB2/HelloWorldEJB!com.jsebfranck.jboss.ejb2.session.HelloWorldEJBHome");
      HelloWorldEJB helloWorldEjb = helloWorldEJBHome.create();
      response.getWriter().println("EJB session test : " + helloWorldEjb.helloWorld());

      // EJB 2 entity
      MemberHome memberHome = (MemberHome) new InitialContext().lookup("java:global/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome");
      Member member = memberHome.create(25L, "jsebfranck");
      response.getWriter().println("EJB entity test : " + member.getId() + " - " + member.getLogin());
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

And we have the prove that this example is working :-)


Conclusion

I hope this example helped you to migrate your EJB2 to a newer version of your application server. Don't hesitate to contact me if you need further details to advance in your migration.


14 commentaires:

  1. Hi Jean,

    Thank you for the article, I've the following doubts:
    1. I would like to know the list of *.jar files you've used like jboss-j2ee.jar. Also, let me know, where you placed those jar files.

    2. In ejb-jar.xml, you've used EJB3.1 version by using the following tag:


    In my case, I've the following tag:


    Please let me know how to migrate from ejb-jar_2_0.dtd to ejb-jar_3_1.xsd. Meanwhile, I'm reading both the dtd and xsd files.

    3. Also let me know the meanings of following tags in standalone-full.xml while configuring datasource:


    false
    false
    FailingConnectionOnly


    Please let me know or post some links explaining them.

    Literally, you have saved lot of hours of mine by providing the example.

    Thanks again,

    Diesel

    RépondreSupprimer
  2. The tags which I've used in 2 question are
    In ejb-jar.xml, you've used EJB3.1 version by using the following tag:

    ejb-jar version="3.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"
    In my case, I've the following tag:

    DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd"

    Similarly, in question 3, the tags are

    pool
    prefill false prefill
    use-strict-min false use-strict-min
    flush-strategy FailingConnectionOnly flush-strategy
    pool

    RépondreSupprimer
  3. Hi,

    I'm glad this example will help you.

    1) In this example, the generated .war file don't contain any .jar, all the needed jar are directly in jboss 7 folders.
    2) I remember I tried to do the deployment with EJB 2.0 tag but it didn't work so I just change the header of the ejb-jar.xml to use the 3.1 tag. That's all I changed.
    3) Configuring a datasource in jboss 7 is a large topic because it is not obvious at all. I faces a lot of difficulties to configure an oracle datasource. In this example, I just took the parameters of another example with hsql. So you should refer to JBoss documentations of further details.

    RépondreSupprimer
  4. Nice example to start migration from JBoss4 to JBoss7.
    Do you have any experience with auto-increment primary keys? Works fine in JBoss4 and stopped working in JBoss7.
    Thanks for any advice. Dusan.

    RépondreSupprimer
    Réponses
    1. Hi Dusan,

      How do you increment your keys? Do you use a custom generator using a database sequence? Something else?

      Supprimer
  5. Please note that I have published the source code here : https://github.com/jsebfranck/jee-samples.

    RépondreSupprimer
  6. I tried this example and for some reason my ejb is not detected as I don't see the information below and ultimately i get the error javax.naming.NameNotFoundException..

    19:07:56,689 INFO [org.jboss.as.server.deployment] (MSC service thread 1-2) JBAS015876: Starting deployment of "WarWithEJB2.war"
    19:07:57,069 INFO [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-4) JNDI bindings for session bean named MemberEJB in deployment unit deployment "WarWithEJB2.war" are as follows:

    java:global/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.Member
    java:app/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.Member
    java:module/MemberEJB!com.jsebfranck.jboss.ejb2.entity.Member
    java:global/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome
    java:app/WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome
    java:module/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome

    RépondreSupprimer
    Réponses
    1. Are you sure to launch your jboss server with the standalone-full configuration? --> standalone.sh --server-config=standalone-full.xml

      Supprimer
    2. Thanks for your reply and I actually am using Eclipse and I am using the standalone-full.xml configuration. Remember the ejbs are inside the war as you have stated and only the war is deployed. Am I missing something. Can you share your deployed war? The other question I wanted to ask is does it use the jboss-ejb-client.properties internally as stated in the link below https://docs.jboss.org/author/display/AS72/EJB+invocations+from+a+remote+client+using+JNDI

      Supprimer
  7. Hi, thanks a lot for your tutorial! I'm actually facing a similar challenge: migrate an ejb 2 application (swing client) from Jboss 4 to Jboss 7.
    I was able to run your code locally, but I tried to connect the session bean using a remote client instead of a Servlet.

    This is how I did:
    Hashtable properties = new Hashtable();
    properties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
    properties.put(Context.PROVIDER_URL, "remote://localhost:4447");
    properties.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT", "false");

    InitialContext ic = new InitialContext(properties);
    Object o = ic.lookup("ejb:j7e2/j7e2-ejb-1.0.0//HelloWorldEJB!be.mil.ejb2.session.HelloWorldEJBHome");
    HelloWorldEJBHome helloWorldEJBHome = (HelloWorldEJBHome) o;
    HelloWorldEJB helloWorldEjb = helloWorldEJBHome.create();

    ... and here comes the error: java.lang.IllegalStateException: No EJB receiver available for handling [appName:j7e2,modulename:j7e2-ejb-1.0.0,distinctname:]

    I'm nearly sure about my appName/modulename as my bean deployed like this in jboss:
    java:global/j7e2/j7e2-ejb-1.0.0/HelloWorldEJB!be.mil.ejb2.session.HelloWorldEJBHome

    Any Idea?

    RépondreSupprimer
  8. Hi,When i tried this example without Entity bean then it was running properly but when i implement Entity bean logic and tried to connect my developement database(Oracle 11g) that time i am getting error like

    12:58:09,660 INFO [org.jboss.ejb.client] (http--127.0.0.1-8080-1) JBoss EJB Client version 1.0.5.Final
    12:58:09,687 ERROR [stderr] (http--127.0.0.1-8080-1) javax.naming.NameNotFoundException: WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome -- service jboss.naming.context.java.global.WarWithEJB2."MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome"
    12:58:09,690 ERROR [stderr] (http--127.0.0.1-8080-1) at org.jboss.as.naming.ServiceBasedNamingStore.lookup(ServiceBasedNamingStore.java:97)
    12:58:09,691 ERROR [stderr] (http--127.0.0.1-8080-1) at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:178)
    12:58:09,692 ERROR [stderr] (http--127.0.0.1-8080-1) at org.jboss.as.naming.InitialContext.lookup(InitialContext.java:123)
    12:58:09,694 ERROR [stderr] (http--127.0.0.1-8080-1) at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:214)
    12:58:09,695 ERROR [stderr] (http--127.0.0.1-8080-1) at javax.naming.InitialContext.lookup(InitialContext.java:392)
    12:58:09,696 ERROR [stderr] (http--127.0.0.1-8080-1) at com.jsebfranck.jboss.servlet.Ejb2Servlet.doGet(Ejb2Servlet.java:29)
    12:58:09,697 ERROR [stderr] (http--127.0.0.1-8080-1) at javax.servlet.http.HttpServlet.service(HttpServlet.java:734)
    12:58:09,698 ERROR [stderr] (http--127.0.0.1-8080-1) at javax.servlet.http.HttpServlet.service(HttpServlet.java:847)
    12:58:09,699 ERROR [stderr] (http--127.0.0.1-8080-1) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:329)
    12:58:09,716 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/WarWithEJB2].[Ejb2Servlet]] (http--127.0.0.1-8080-1) Servlet.service() for servlet Ejb2Servlet threw exception: java.lang.RuntimeException: javax.naming.NameNotFoundException: WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome -- service jboss.naming.context.java.global.WarWithEJB2."MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome"
    at com.jsebfranck.jboss.servlet.Ejb2Servlet.doGet(Ejb2Servlet.java:34) [classes:]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:734) [jboss-servlet-api_3.0_spec-1.0.0.Final.jar:1.0.0.Final]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:847) [jboss-servlet-api_3.0_spec-1.0.0.Final.jar:1.0.0.Final]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:329) [jbossweb-7.0.13.Final.jar:]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:248) [jbossweb-7.0.13.Final.jar:]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:275) [jbossweb-7.0.13.Final.jar:]
    Caused by: javax.naming.NameNotFoundException: WarWithEJB2/MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome -- service jboss.naming.context.java.global.WarWithEJB2."MemberEJB!com.jsebfranck.jboss.ejb2.entity.MemberHome"
    at org.jboss.as.naming.ServiceBasedNamingStore.lookup(ServiceBasedNamingStore.java:97)
    at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:178)
    at org.jboss.as.naming.InitialContext.lookup(InitialContext.java:123)
    at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:214)
    at javax.naming.InitialContext.lookup(InitialContext.java:392) [rt.jar:1.6.0_06]
    at com.jsebfranck.jboss.servlet.Ejb2Servlet.doGet(Ejb2Servlet.java:29) [classes:]
    ... 15 more
    Please help me out...

    RépondreSupprimer
  9. Hi, I wonder if you might have advice for me, I'm getting the error JBAS014154: Failed to marshal EJB parameters. The problem is I have a CMR relationship. When I try and pass one EJB object to the create method of another EJB method to set the relationship, it can't marshal up remote objects. This code used to work for me with some older version of jboss, so the code itself is basically sound. I think I have to do something different to not get a remote object but rather a local object so it doesn't try and marshal it. As I understand it, whenever you do a lookup on "java:global...." it gets a remote object, but I can't find out how to do a lookup and get a local object.

    RépondreSupprimer
  10. perfect explanation about java programming .its very useful.thanks for your valuable information.java training in chennai | java training in velachery

    RépondreSupprimer
  11. Congratulations guys, quality information you have given!!!..Its really useful blog. Thanks for sharing this useful information
    java training institutes in chennai | java j2ee training institutes in velachery

    RépondreSupprimer