Tuesday, November 2, 2010

Accessing multiple remote EJB 3.0 Session Beans via JNDI in an SE Client - Experiment

Update: This project is superceeded by the Collatz Distributed Java EE Application Tutorial
Note: This code will run on both a Java SE client or an EE container like Oracle WebLogic.

Here as a preparation for using or understanding how a distributed java application runs under a shared level 2 cache before we actually use one. We can develop our own naive cache by referencing ''session beans'' on one or more WebLogic server applications acting as a distributed application and possibly using our own HashMap - old school and unnecessary but part of essential learning.




WebLogic RMI Client Configuration
Here we have a very simple JNDI client that acts as a hello world distributed host and manages remote session beans on two physical WebLogic servers.
The JNDI protocol uses t3
The JNDI factory class for WebLogic is weblogic.jndi.WLInitialContextFactory
The default JNDI name for you SSB can be redifined via the mappedName annotation to ejb/Node with the corresponding remoteReference as java:comp/env/ejb/Node
The key to using an EJB 3.0 session bean remotely is to narrow the Object to the bean interface.
The following stateful session bean goes into an EAR and is deployed on each EE container in your cluster.

@Stateful(mappedName="ejb/Node")
@Local
public class Node implements NodeRemote, NodeLocal {
    private int transition;
    private int state;        
    public void setState(int aState) {
        System.out.println("_Distributed: new state: " + aState);
        state = aState;
    }
    public int getState() { return state;  }
}
This goes on your SE client.

public class JNDIClient {
    public static final List<String serverNames = new ArrayList<String>();
    private Map<String, Hashtable<String, String>> contextMap = new HashMap<String, Hashtable<String, String>>();
    private Map<String, Context> rmiContextMap = new HashMap<String, Context>();    
    private Map<String, NodeRemote> remoteObjects = new HashMap<String, NodeRemote>();    
    private Map<String, Integer> stateToSet = new HashMap<String, Integer>();
    private static Map<String, String>  serverIPMap = new HashMap<String, String>();
    // How many processors are available (real + hyperthreaded)
    private static int availableProcessors; 
    private static final String CONTEXT_FACTORY_NAME = "weblogic.jndi.WLInitialContextFactory";
    private static final String SESSION_BEAN_REMOTE_NAME = "ejb/Node#org.eclipse.persistence.example.distributed.NodeRemote"; 
    //private String sessionBeanRemoteName = "java:comp/env/ejb/Node"; // EE only
    //private String sessionBeanRemoteName = "org_eclipse_persistence_example_distributed_ClientEARorg_eclipse_persistence_example_distributed_ClientEJB_jarNode_Home" ;

    /**
     * For each server add the name key and corresponding RMI URL
     */
    static {
        // Beowulf5 on Cat6 1Gb/Sec
        serverNames.add("beowulf5");
        serverIPMap.put("beowulf5", "t3://192.168.0.165:7001");
        // Beowulf6 on Cat6 1Gb/Sec
        serverNames.add("beowulf6");
        serverIPMap.put("beowulf6", "t3://192.168.0.166:7001");
        availableProcessors = Runtime.getRuntime().availableProcessors();
    }

    public void connect() {
        /**
         * Note: only import weblogic.jar and naming.jar
         */
        // For JNDI we are forced to use Hashtable instead of HashMap
        // populate the hashtables        
        for(String aServer : serverNames) {
            Hashtable aTable =  new Hashtable();
            contextMap.put(aServer,aTable);
            aTable.put(Context.INITIAL_CONTEXT_FACTORY,CONTEXT_FACTORY_NAME);
            aTable.put(Context.PROVIDER_URL, serverIPMap.get(aServer));
        }
        System.out.println("Available Processors on this System: " + availableProcessors);
        /**
         * Setup and connect to RMI Objects
         */
        try {            
            // Establish RMI connections to the session beans
            for(String aServer : serverNames) {
                Context aContext = new InitialContext(contextMap.get(aServer));
                rmiContextMap.put(aServer, aContext);
                System.out.println("Context for " + aServer + " : " + aContext);
                // For qualified name look for weblogic log "EJB Deployed EJB with JNDI name"
                Object aRemoteReference = aContext.lookup(SESSION_BEAN_REMOTE_NAME);        
                System.out.println("Remote Object: " + aRemoteReference);        
                NodeRemote aNode = (NodeRemote) PortableRemoteObject.narrow(aRemoteReference, NodeRemote.class);
                remoteObjects.put(aServer, aNode);
                System.out.println("Narrowed Session Bean: " + aNode);        
                // initialize state list
                stateToSet.put(aServer, new Integer(0));
            }
            
            NodeRemote aNode;
            StringBuffer aBuffer = new StringBuffer();           
            // Endlessly generate RMI requests
            for(;;) {
                // Send messages to entire grid in parallel
                /**
                 * Issue: One JVM halt will affect the entire distributed app.
                 */
                for(String remoteServer : remoteObjects.keySet()) {
                    aNode = remoteObjects.get(remoteServer);
                    // increment server's pending state
                    stateToSet.put(remoteServer, stateToSet.get(remoteServer).intValue() + 1);
                    try {
                        aNode.setState(stateToSet.get(remoteServer));
                        aBuffer = new StringBuffer("State from: ");
                        aBuffer.append(remoteServer);
                        aBuffer.append(" = ");
                        aBuffer.append(aNode.getState());                   
                        System.out.println(aBuffer.toString());
                    } catch (EJBException e) {
                        //  weblogic.transaction.internal.TimedOutException: Transaction timed out after 29 seconds 
                        e.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        JNDIClient client = new JNDIClient();
        client.connect();
    }
}


The key point is to rename your session bean and reference it by name#application.

Java EE Stateless Session Bean

@Stateless(mappedName = "ejb/CollatzFacade")
public class CollatzFacade implements CollatzFacadeRemote, CollatzFacadeLocal {
Java SE Client
    private static final String SESSION_BEAN_REMOTE_NAME = 
      "ejb/CollatzFacade#org.eclipse.persistence.example.distributed.collatz.business.CollatzFacadeRemote"; 




GlassFish 3.1 and Remote EJB clients
GlassFish 3.1 no longer ships with the non-OSGI gf-client.jar, however the actual OSGI gf-client-module.jar that the manifest in gf-client wraps is still in the modules directory.
see:
http://java.net/jira/browse/GLASSFISH-13295

GlassFish 3.0.1 Results:
You don't need to specify the HashTable parameter for the InitialContext connection for GlassFish.

run:
Context for xps435 : javax.naming.InitialContext@c8f6f8
Feb 6, 2011 6:53:18 PM com.sun.enterprise.transaction.JavaEETransactionManagerSimplified initDelegates
INFO: Using com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate as the delegate
Remote Object: org.dataparallel.collatz.business._CollatzFacadeRemote_Wrapper@498cb673
Narrowed Session Bean: org.dataparallel.collatz.business._CollatzFacadeRemote_Wrapper@498cb673

1 comment: