Saturday, July 14, 2012

JBoss AS7 JNDI & EJB 3.1 Naming changes

As a result of the "feature train" continuing to march on and us not keeping our software stack up-to-date, our team finds ourselves in the un-enviable position of having to migrate:

  • JBoss 4.2.3 to AS 7.1.x (currently looking at 7.1.1)
  • EJB 2.1 to EJB 3.1
  • Hibernate 2 to Hibernate 3 or 4
in quick fashion.  I mean, who wants to ship a new release with 8-10 year old software, not me!

The following is the result of some research I did while looking at upgrading from JBoss 4.2.3 with EJB 2.1 to AS 7.1.x with EJB 3.1.  I am sure there will be more posts related to this migration in the near future, but this one is related to changes in the JNDI naming area.

In the past/current
In our current code, the JNDI naming has been very simple:
  1. We concatenated "ejb/" with the name of the remote Session Bean interface in the deployment descriptor to indicate the name that the service should be bound to.
  2. In the code, we use the following code to handle the JNDI lookup.  The really nice part was that the same code could be used by remote clients AND on the server, within the container.
Current Code

Hashtable properties = new Hashtable();
properties.put("java.naming.factory.initial","org.jnp.interfaces.NamingContextFactory");
properties.put("java.naming.factory.url.pkgs","org.jboss.naming:org.jnp.interfaces");
properties.put("java.naming.provider.url", "jnp://localhost:1099");
Context ctx = new InitialContext(properties);
Object ref = ctx.lookup(jndiName);


AS7 and EJB 3.1
The EJB 3.1 spec has made some changes to mandate portable JNDI names for EJBs and you inherit this in AS 7.1.1.   The other tricky thing I found was that I could no longer use the exact same lookup code from our remote clients and the server.

AS 7 now has two options for remote EJB invocation.  The information you need can be found in the JBoss docs, it just didn't hit me over the head!  After struggling with this issue for a couple days, I decided to create a small program to help make the differences (hopefully) very clear.  Below is the program that I created to invoke a stateless session bean from a remote client.   I deployed the "ejb-remote" sample from the 7.1 Quick Start samples.  The code attempts to load the remote service using both remote methods.  The expectation is that for the first set of lookups, the first lookup is successful and the second, using the "ejb:/" naming format fails.  Then I add the Context.URL_PKG_PREFIXES property with a value of "org.jboss.ejb.client.naming" to the jndi properties passed to the InitialContext constructor and repeat the lookups.  Now, both lookups should be successful.  I have included all the JNDI properties in the code rather than relying on a copy of "jboss-ejb-client.properties" or "jndi.properties" being picked up from the classpath. 
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import java.util.Hashtable;

public class EJBClient {

     private static String[] JNDINAME = {
          "jboss-as-ejb-remote-app/CalculatorBean!org.jboss.as.quickstarts.ejb.remote.stateless.RemoteCalculator", 
          "ejb:/jboss-as-ejb-remote-app/CalculatorBean!org.jboss.as.quickstarts.ejb.remote.stateless.RemoteCalculator" 
     };
     
     private Hashtable jndiProps;
     
     public EJBClient() {
       // setup 'base' jndi properties - no jboss-ejb-client.properties being picked up from classpath!
       jndiProps = new Hashtable();
          jndiProps.put("java.naming.factory.initial","org.jboss.naming.remote.client.InitialContextFactory");
          jndiProps.put(InitialContext.PROVIDER_URL, "remote://localhost:4447");
          jndiProps.put("jboss.naming.client.ejb.context", true);
        
          // needed for remote access - remember to run add-user.bat
          jndiProps.put(Context.SECURITY_PRINCIPAL, "client");
          jndiProps.put(Context.SECURITY_CREDENTIALS, "password");
     }
     
    public void doLookups() {
         // the 'exported' namespace
         for (int i = 0; i < JNDINAME.length; i++) {
               lookup(JNDINAME[i]);
          }
         
        
         // This is an important property to set if you want to do EJB invocations via the remote-naming project
        jndiProps.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
        
        // now with the ejb
        for (int i = 0; i < JNDINAME.length; i++) {
               lookup(JNDINAME[i]);
          }
    }
    
    private void lookup(String name) {
         System.out.println("Lookup name="+name);
         
         Context ctx = null;
        try {
               ctx = new InitialContext(jndiProps);
               Object ref = ctx.lookup(name);
               System.out.println("...Successful");
          } catch (NamingException e) {
               System.out.println("...Failed");
               //System.out.println(e.getMessage());
               e.printStackTrace();
          } finally {
               if (ctx != null) {
                    try {
                         ctx.close();
                    } catch (NamingException e) {}
               }
          }
    }
    
    public static void main(String[] args) throws Exception {
         EJBClient client = new EJBClient();
         client.doLookups();
         
         System.out.println("Done!");
    }
    
}

AS7 on the server-side
Now the easy part, doing the JNDI lookups on the server are very similar to the old way, except that you still need to format the JNDI name according to the new specs and the naming factory is no longer the jnp version!

Hashtable jndiProps = new Hashtable();
    jndiProps.put("java.naming.factory.initial", "org.jboss.as.naming.InitialContextFactory");
    ctx = new InitialContext(jndiProps);
    Object ref = ctx.lookup(jndiName);

Conclusion
As I said earlier, the information was/is in the JBoss documentation, but I probably read past it several times.  What I was looking for was an example showing loading the EJBs from both the client and the server.

Hope this helps!

2 comments:

  1. I am using JBOSS 7.2.0.Final

    I got following exception :

    Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at org.jboss.ejb.client.remoting.RemotingConnectionEJBReceiver.(RemotingConnectionEJBReceiver.java:102)
    at org.jboss.ejb.client.remoting.RemotingConnectionEJBReceiver.(RemotingConnectionEJBReceiver.java:90)
    at org.jboss.ejb.client.EJBClientContext.registerConnection(EJBClientContext.java:420)
    at org.jboss.naming.remote.client.ejb.RemoteNamingEjbClientContextSelector.getContext(RemoteNamingEjbClientContextSelector.java:60)
    at org.jboss.naming.remote.client.ejb.RemoteNamingEjbClientContextSelector.getCurrent(RemoteNamingEjbClientContextSelector.java:46)
    at org.jboss.naming.remote.client.ejb.RemoteNamingEjbClientContextSelector.getCurrent(RemoteNamingEjbClientContextSelector.java:15)
    at org.jboss.ejb.client.EJBClientContext.getCurrent(EJBClientContext.java:271)
    at org.jboss.ejb.client.EJBClientContext.requireCurrent(EJBClientContext.java:281)
    at org.jboss.ejb.client.EJBInvocationHandler.doInvoke(EJBInvocationHandler.java:156)
    at org.jboss.ejb.client.EJBInvocationHandler.invoke(EJBInvocationHandler.java:124)
    at com.sun.proxy.$Proxy0.echoPS(Unknown Source)
    at com.abc.client.SampleClient$2.actionPerformed(SampleClient.java:73)
    at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2018)
    at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2341)
    at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
    at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
    at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:252)
    at java.awt.Component.processMouseEvent(Component.java:6505)
    at javax.swing.JComponent.processMouseEvent(JComponent.java:3321)
    at java.awt.Component.processEvent(Component.java:6270)
    at java.awt.Container.processEvent(Container.java:2229)
    at java.awt.Component.dispatchEventImpl(Component.java:4861)
    at java.awt.Container.dispatchEventImpl(Container.java:2287)
    at java.awt.Component.dispatchEvent(Component.java:4687)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4832)
    at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4492)
    at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4422)
    at java.awt.Container.dispatchEventImpl(Container.java:2273)
    at java.awt.Window.dispatchEventImpl(Window.java:2719)
    at java.awt.Component.dispatchEvent(Component.java:4687)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:729)
    at java.awt.EventQueue.access$200(EventQueue.java:103)
    at java.awt.EventQueue$3.run(EventQueue.java:688)
    at java.awt.EventQueue$3.run(EventQueue.java:686)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:87)
    at java.awt.EventQueue$4.run(EventQueue.java:702)
    at java.awt.EventQueue$4.run(EventQueue.java:700)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:699)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:146)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:138)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:91)

    ReplyDelete
  2. very helpful. The difference between standalo

    ReplyDelete