HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

One unfortunate aspect of the many enterprise APIs and specifications that Sun has released over the last few years has been the lack of information about how some of these APIs interact with one another. In particular, two very useful specifications the Java Messaging Service (JMS) and Enterprise JavaBeans (EJB) have been released and already implemented individually by many application server vendors. What wasn't considered in this process, at least at this stage, is how the two would (or should) work together. Enterprise JavaBeans is a server-side component architecture, which provides remote, distributed, secure objects with built-in transaction support. JMS is a portable messaging service architecture and API that provides for persistent, point-to-point or publish/subscribe messaging with transaction support.

Communication with, or between, EJBs is a synchronous mechanism of one client method (perhaps another EJB) invoking a method on an EJB, with a possible return value. JMS, on the other hand, has both synchronous and asynchronous capabilities for sending messages between clients. It would seem that using JMS to pass asynchronous messages (or even method invocations) between EJBs would be a useful facility. Unfortunately, Sun has yet to tie these two specifications together and there is currently no standard mechanism to use JMS with EJBs (although there seems to be the promise of Sun delivering such a spec in the not-too-distant future).

In this article I'll show you how to circumvent this problem with an adapter-style delegation model that uses some of the inherent features of the BEA/WebLogic application server and its implementation of both JMS and EJBs. This model, while making use of a somewhat proprietary feature of the application server, can easily be extended if so desired to a more independent and portable mechanism by implementing it as a stand-alone Java service.

If you want to test the example code included with this article, I would recommend that you first obtain and install the latest release of the BEA/WebLogic application server (this article is based on version 4.5.1). You can download a free 30-day trial copy from BEA at www.bea.com.

The Java Messaging Service
The JMS is a set of interfaces implemented by vendors of message-oriented middleware (MOM), application servers and database servers that wish to support messaging within their products. JMS provides a simple, common API for client applications to implement code that uses portable messaging, against potentially any given number of underlying messaging systems. (Because JMS is designed to be portable, it's important to realize that as a result, if you're familiar with any given MOM product, it isn't certain that JMS will support every aspect of that product.)

The primary concept in JMS is that of Destinations. A Destination is nothing more than an association for message producers and message consumers. Destinations break down into two types, Topics or Queues. For the purposes of this article we'll discuss only Queues, which implement point-to-point messaging. (Using a Topic to support the implementation described in this article should be a fairly easy substitution, however.) Both Queues and Topics support persistence. An incoming message will be stored in a persistent Queue until a QueueReceiver connects to it and receives the message synchronously through a receive() call, or passes it to a registered MessageListener. This latter mechanism provides an asynchronous message delivery model. JMS also provides support for transactions in a very basic form through the standard Connection/Session creation mechanisms. Alternately, JMS provides an XA implementation that is by default transacted and will participate in the context of a distributed transaction. A full description of transaction support is beyond the scope of this article. If you're unfamiliar with the JMS, I recommend my tutorial "Using the Java Messaging Service with BEA/WebLogic" published in the January 2000 issue (JDJ, Vol. 5, issue 1).

Enterprise JavaBeans
EJBs are remote distributed server-side components that have built-in support for transactions and security. An EJB is a self-contained component that runs within the confines of an EJB container. The container, and its underlying server, are typically supplied by an application server provider. It's the duty of the container to manage any EJBs running within it by taking responsibility for:

  • Managing the state of an EJB
  • Instantiating, pooling, removing and activating the EJB
  • Making callbacks to the EJB to tell it to load or store its state (with container-managed persistence, the container can even manage the loading and storing of the EJB's state)
  • Thread safety (by default EJBs are single-threaded, and mustn't create their own threads)
Because EJBs are designed with inherent support for transactions, the container also manages the coordination of the EJB with an underlying Transaction Manager (the transaction may be a distributed transaction that in turn is coordinated with various resource managers for access to one or more databases).

Transaction management is therefore fairly transparent to EJB developers, requiring them to set a couple of DeploymentDescriptor attributes to specify how the EJB will participate in the context of a transaction (or even not at all). The DeploymentDescriptor allows the EJB to set certain parameters at the time it's deployed so the application server can change aspects of how the EJB is handled by the container (such as how it's pooled, how it participates in a transaction or its security attributes) without changing the EJB's code, or having to recompile it.

EJBs also provide a security model that can control access at the method level for any specific EJB. In this way you could have an EJB with five methods, four of which were available to all users and one of which was restricted to a manager only, or an administrator constituting very fine-grained security access control for an EJB.

One final aspect of EJBs is that by default they're distributed components. In this sense they're remote and are accessed in the same way RMI objects are. In fact, the various EJB interfaces (EJBHome, EJBObject) extend the java.rmi.Remote interface and methods of the EJBObject-derived remote interface throw java.rmi.RemoteException. EJBs are typically registered with JNDI (WebLogic does this through the weblogic.properties file at startup). Access to the EJB is a JNDI lookup operation for an EJB's home interface, which then acts as a type of factory for creation or location of the actual EJB itself.

One important thing to remember with EJBs is that you're always dealing with a "remote" stub class, you're never accessing the actual EJB directly (that is, you're never directly accessing the class that actually implements the functionality provided by the EJB usually referred to as the "bean" class). Access to the actual functionality of the EJB is through calls to the stub class (which implements the EJB's remote interface). The stub communicates with the bean class through the container. The container controls access to the bean class and manages calls to the EJB, usually within a transaction context. It is this aspect of EJBs, though their distributed, remote nature that leads us to the problem of using EJBs with the JMS.

The Problem: Using JMS with EJBs
The problem in trying to use JMS with EJBs derives from the way you access an EJB to perform a method invocation on the bean. Specifically, the problem concerns the fact that an EJB doesn't behave like a standard "daemon service" or remote object in the sense that it isn't continuously up and running and waiting for access. EJBs are available during an access, but at any point after the access after a specific timeout period or when the last reference to a bean is released it can be passivated and put back into an available pool of beans by the container. Remember that you never have access to the bean itself, but only to a client-side stub class that implements the EJB's remote interface. A reference to the stub doesn't guarantee that the bean is active on the server or available on it. (The bean may have been passivated due to a timeout, for example, or the object pool might be at its maximum.) The key point here is that the EJB isn't guaranteed to be constantly running and available simply because your code might have a reference to the EJB's remote interface.

On the messaging side, JMS provides a mechanism to send and receive messages to and from a Destination. Let's take the example of one specific Destination type, a Queue. You create a QueueConnection and QueueSession, and from the QueueSession you ultimately create a QueueSender or QueueReceiver. The QueueSender is used to send a JMS message to a Queue. Any QueueReceiver connected to the same Queue can retrieve incoming messages on the Queue synchronously or asynchronously. For a message to be delivered asynchronously, a QueueReceiver must have a registered MessageListener to which it can pass an incoming asynchronous message. An object that wishes to be notified of asynchronous messages must then implement the MessageListener interface and its single method onMessage(). Once this object is registered with the QueueReceiver for a specific Queue, any incoming message on that Queue will be passed to the onMessage() implementation of that object as a callback from JMS.

Sending a JMS message to a Queue is a simple matter for an EJB. When a method on an EJB is invoked, the bean can simply create the appropriate JMS Queue objects, retrieve the Queue from JNDI and ultimately send a specific JMS message to the Queue. But what happens if we want our EJB to receive a JMS message? At first glance it might seem that you could simply implement the MessageListener interface in your EJB's bean class, implement the onMessage() method and register this with a QueueReceiver. But this implementation violates one of the major rules of EJBs and is therefore illegal and potentially disastrous. Why?

It all comes back to two major points discussed above: an EJB bean class should never be accessed directly, but only through its remote interface; and the Container manages the availability of an EJB.

If you implement the MessageListener in the bean class, JMS is essentially calling directly into the EJB and not through the remote interface. Thus JMS is bypassing the container and directly making an invocation on the bean class. Because the container is managing not only the EJB but also transactions (as well as thread safety), it would be a potentially catastrophic operation if JMS were suddenly to call into your bean class directly while it's in the middle of a transaction or other operation. In addition, unless the EJB bean class was actually available, it wouldn't be possible to register the bean class with the QueueReceiver until the bean was in an instantiated state. (Also, depending on how such a scenario was coded, JMS might be calling into a bean instance while it was in the container's object pool, as well as calling into activated instances that were in communication with a client.) Figure 1 illustrates this kind of illegal use.

Figure 1
Figure 1:

The Solution: WebLogic Startup Classes
The way to solve this problem is to use a delegation model. In the model I'm going to present we'll implement a delegator class (a kind of adapter) for receiving and forwarding JMS messages to an EJB. We can implement this delegation class using a proprietary feature of the WebLogic application server called "startup classes." A startup class is simply a Java class that implements the weblogic.common.T3StartupDef interface from the WebLogic packages.

A startup class is registered in the weblogic.properties file and, as the name implies, will be loaded by the WebLogic server upon startup and run within the application server's VM instance. The T3StartupDef interface defines one method called startup(), which is passed a String identifier and a Hashtable that contains any parameters you wish to pass to your startup class these parameters are set in the weblogic.properties file. The startup() method is the equivalent of a main() method, in a sense, and it will be called by the WebLogic server when your startup class is instantiated.

Next we need to create a special Queue (see the WebLogic documentation for details on setting up the Queue for the WebLogic application server) in the WebLogic server for our incoming JMS messages. We'll use this special Queue when sending messages intended for an EJB via the startup class delegator. In turn, the startup class is set up to listen on this same Queue for JMS messages intended for an EJB. We'll use this model for two types of EJB access. The first is a direct delegation model in which we'll pass the incoming JMS message on to an onMessage() method in our example EJB. The second is an asynchronous method invocation on an EJB via a JMS message (see Figure 2). Both forms actually use the same underlying code, as you will see.

Figure 2
Figure 2:

Using the Delegation Model
Our startup class should contain code within its startup() method that initializes JMS and implements the JMS MessageListener interface and its onMessage() method. Listing 1 shows the code for our JmsQueueManager class this class implements all of the functionality required to create a JMS QueueConnection, QueueSession, QueueReceiver and QueueSender. It also provides utility methods that retrieve a Queue via JNDI and shuts down the JMS connection in its finalize() method.

The important method in JmsQueueManager is initializeJMS(). This initializes JMS, creates the JMS QueueConnection, QueueSession, retrieves the Queue through JNDI and creates the QueueSession and/or QueueReceiver.

We extend this base class in our WebLogic startup class. This gives our startup class all the JMS functionality it needs to manage a JMS Connection. Our startup class also implements the JMS MessageListener interface and its onMessage() method. Once JMS is successfully initialized through the base class code, any incoming JMS messages sent to our Queue will cause an invocation of the onMessage() method in the startup class. The onMessage() method, the core of the startup class, is where the actual delegation occurs. We do the delegation through the use of Java's reflection mechanism and by parsing out method names and parameters from the contents of a JMS message. The startup class is shown in Listing 2.

Asynchronous Method Invocation with JMS
The onMessage() method of the startup class extracts predefined parameters from an incoming JMS message. In the code listing this message is assumed to be a JMS MapMessage that can contain multiple name/value pairs. A value is stored in conjunction with a String "name." You look up the value by passing the String name to an appropriate method, based on the type of the value for instance, "getString(String name)" will look for a String value based on "name." It uses the values of these parameters to obtain the EJBHome class, first. This is important since we need the EJBHome to either create or locate an EJB. Then, using the Java reflection mechanism, we utilize additional JMS message parameters to determine the type of parameters for two methods; the EJBHome's create() method (this could also support an EntityBean's findByPrimaryKey() method) and then the actual method to be invoked on the remote interface.

In the code listing I've hard-coded the String "names" for the MapMessage's values for clarity (these could be turned into "final static" constants). This shows how you can use a single JMS message to package all the information needed for the delegator so it can locate the EJBHome, use reflection to create the EJB and if the remote interface stub class is successfully retrieved invoke a method on the stub class passing in parameters from the MapMessage. Thus the delegation startup class is making asynchronous invocations against the EJB. The code for the ReceiverStartup class in Listing 2 shows how to do this.

This mechanism uses some support methods to obtain the Class of the parameters for both a create method and an EJB's remote interface method, and to determine and set the parameter's values. Once these have been created, the method is actually invoked through reflection.

Delegating the JMS Message
An important point to note is that we use the same block of code in onMessage() to invoke a method asynchronously on an EJB and to pass on a JMS message to an EJB.

The code in Listing 3 shows how we would do this. This is a simple client class that can be used to test our delegator. It creates two separate JMS messages. The first sets its parameter to type "message." Doing this instructs the ReceiverStartup onMessage() code to look for the method name in the remote interface stub, but to pass on the JMS message to the EJB's method (this method, of course, must take a parameter that is a JMS message). In this way the ReceiverStartup class performs what appears to be a simple pass through of the message to the EJB. The second message in the client code listing sends a message with multiple parameters that invokes a simple EJB method called testMethod() that takes an integer and String as parameters. Thus we have one block of code in the ReceiverStartup class that can be used for any asynchronous EJB invocation, including passing a JMS message on to the EJB itself (although you must add additional parameters to the message so that the EJB can be located).

Some additional features that could be added to the code would be dealing with return values using the JMSReplyTo property of a JMS message (not implemented in the code listings in this article I leave you to investigate this for yourself). You could specify a "return value" Queue on which you could pass back a message with a unique identifier and the returned results from the method that was invoked. Also, in the code listings I use a JMS MapMessage that requires us to add the specific name/values that we'll need to locate the EJB and invoke a method on it. This could be enhanced by wrapping or extending the base JMS message classes to add methods to handle these unique name/values, while leaving the underlying JMS message alone.

Summary
There's little doubt that EJBs and JMS are both very useful and powerful enterprise technologies when used independently. Although Sun has yet to define how these two specifications should interact, the model presented here demonstrates one solution to the current problem developers face when trying to use these technologies together. It's my hope that the ideas presented in this article demonstrate the potential for using JMS and EJBs in the enterprise. I encourage you to experiment with the concepts presented and create your own EJB and JMS solutions.

Author Bio
Scott Grant is chief architect and lead developer for CascadeWorks, Inc., a San Francisco-based ASP company providing B2B solutions. A Sun-certified Java developer with 15 years of diversified engineering experience, Scott previously pioneered Java enterprise e-service solutions at another Bay Area startup company.   [email protected]

	

Lisiting 1 

package jdj.article.jmsejb; 

import javax.naming.*; 
import javax.jms.*;          // Import the javax.jms package 
import java.util.Hashtable; 

 * JmsQueueManager.java 
 * 
 * An abstract base class that implements all the basic 
 * functionality for creating a JMS QueueReceiver or 
 * QueueSender. Includes all the necessary code for obtaining 
 * a JNDI naming Context, looking up the Queue and retrieving 
 * it, setting up the JMS Connection and Session,and creating 
 * the actual QueueReceiver and/or QueueSubscriber. 
 * 
 * This is extended by the ReceiverStartup and the 
 * JMSEjbClient classes, but it could also be used in a "has 
 * a" relationship by creating an implementation class and 
 * then contained as an attribute of our classes through a 
 * reference. 
 * 
 * @author Scott Grant 
 * @version 1.0 - 4/10/00 
 * 
public abstract class JmsQueueManager implements IJmsEjbConstants 
{ 
    protected QueueConnectionFactory queueFx;  // Our factory 
    protected QueueConnection conn;        // Our connection 
    protected Queue queue;                     // Our queue 
    protected Queue tempQueue;        // Our temporary queue 
    protected QueueSession session;           // Our session 
    protected QueueReceiver receiver;         / Our receiver 
    protected QueueSender sender;              // Our sender 
  
    protected String jndiFactory; 
    protected String url; 
    protected String jmsFactory; 
    protected String queueName; 
    protected String principal; 
    protected String credential; 
  
    protected Context ctx;  // our initial JNDI context 
  
 * Obtain an initial context from Weblogic through JNDI (this 
 * will be used to access the JMS factory and topic/queue 
 * information). 

 public JmsQueueManager(String jndiFactory, String url, 
                      String queueName, String jmsFactory, 
                      String principal, String credential) 
    { 
        this.jndiFactory = jndiFactory; 
        this.url = url; 
        this.queueName = queueName; 
        this.jmsFactory = jmsFactory; 
        this.principal = principal; 
        this.credential = credential; 
    } 
  
 protected void finalize() throws Throwable 
 { 
     closeJMS(); 
 } 
  
 protected Context getInitialContext() 
 { 
     // Try and create a new initial context using our 
     // properties... 
        Hashtable env = new Hashtable(); 
        env.put(Context.INITIAL_CONTEXT_FACTORY, jndiFactory); 
        env.put(Context.PROVIDER_URL, url); 
        env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
        env.put(Context.SECURITY_PRINCIPAL, principal); 
        env.put(Context.SECURITY_CREDENTIALS, credential); 
        try 
        { 
            // Try and create a new initial context using 
            // our properties... 
            Context context = new InitialContext(env); 
            return context; 
        } 
        catch(NamingException e) 
        { 
            System.out.println("getInitialContext: Could not 
            obtain initial naming context"); 
            e.printStackTrace(); 
            return null; 
        } 
        catch(Exception e) 
        { 
            System.out.println("getInitialContext: Unknown 
        exception trying to obtain initial naming context"); 
            e.printStackTrace(); 
            return null; 
        } 
 } 
  
 * Initialize JMS - create initial JMS resources, 
 * start connection, etc. 

 protected void initializeJMS(String type, MessageListener 
 listener, boolean transacted) 
 { 
     try 
     { 
         if (ctx == null) 
             ctx = getInitialContext(); 
  
         if (ctx != null) 
         { 
  
             // Get the default queue connection 
             // factory...Destinations (Queues and 
             // Topics) and ConnectionFactory objects are 
             // administered objects - you retreive them 
             // from the Weblogic Application Server via JNDI. 
             queueFx = (QueueConnectionFactory) 
             ctx.lookup(jmsFactory); 

                // Get a QueueConnection from the QueueCon- 
                // nectionFactory 
                conn = queueFx.createQueueConnection(); 

                // Get a QueueSession - auto-acknowledge - 
                // and use the parameter to 
                // determine if it is transacted or not... 
                session = conn.createQueueSession(transact- 
                ed, Session.AUTO_ACKNOWLEDGE); 

                // Get the Queue from JNDI...Queue is an 
                // administered object 
                queue = (Queue) ctx.lookup(queueName); 

 
                if ((type.equals(RECEIVER) || 
                    type.equals(SENDER_RECEIVER)) && 
                    listener != null) 
                { 
                    // Create a Receiver for the Queue... 
                    receiver = session.createReceiver(queue); 
  
                    // Set the listener (this class) 
                    receiver.setMessageListener(listener); 
                } 
  
                if (type.equals(SENDER) || 
                type.equals(SENDER_RECEIVER)) 
                { 
                    sender = session.createSender(queue); 
                } 
                // Start connection... 
                conn.start(); 
         } 
         else 
         { 
             System.out.println("initializeJMS: InitialCon- 
             text was null"); 
             return; 
         } 
     } 
     catch(NamingException e) 
     { 
         System.out.println("initializeJMS: NamingException 
         was thrown"); 
            e.printStackTrace(); 
     } 
     catch(JMSSecurityException e) 
     { 
         System.out.println("initializeJMS: JMSSecurityExcep- 
         tion was thrown"); 
            e.printStackTrace(); 
     } 
     catch(JMSException e) 
     { 
         System.out.println("initializeJMS: JMSException was 
         thrown"); 
            e.printStackTrace(); 
     } 
     catch(Exception e) 
     { 
         System.out.println("initializeJMS: Unknown exception 
         trying to obtain initialize JMS"); 
            e.printStackTrace(); 
     } 
 } 
  
 * Close down JMS - stop the connection, etc. 

 protected void closeJMS() 
 { 
     if (conn != null) 
        { 
            try 
            { 
                conn.stop(); 
                if (sender != null) 
                    sender.close(); 
  
                if (receiver != null) 
                    receiver.close(); 
  
                if (session != null) 
                    session.close(); 
  
                conn.close(); 
            } 
            catch(JMSException e) 
            { 
                System.out.println("closeJMS: JMSException 
                thrown while trying to close connections"); 
                e.printStackTrace(); 
            } 
            catch(Exception e) 
            { 
                System.out.println("closeJMS: An unknown 
      exception occurred while trying to close connections"); 
                e.printStackTrace(); 
            } 
        } 
 } 
  
 * sendMessage 
 * 
 * Send a JMS Message. 

 protected void sendMessage(Message msg) 
 { 
     try 
     { 
         msg.setJMSDeliveryMode(DeliveryMode.PERSISTENT); 
         sender.send(msg); // default behavior 
     } 
     catch(JMSException e) 
     { 
         System.out.println("initializeJMS: JMSException try- 
         ing to send message to queue"); 
     } 
 } 
  
 * createMapMessage 
 * 
 * Creates a MapMessage - delegates to 
 * the session version of this method. 
  
 protected MapMessage createMapMessage() 
 { 
     try 
     { 
         return session.createMapMessage(); // default behavior 
     } 
     catch(JMSException e) 
     { 
         System.out.println("createMapMessage: JMSException 
         trying to create MapMessage"); 
     } 
  
     return null; 
 } 
  
 // You could implement all the additional "createXXXMes0 
 // sage()" delegator methods here, if you wanted to flesh 
 // out this class... 

} 

Listing 2 
package jdj.article.jmsejb; 

import javax.naming.*; 
import javax.jms.*;          // Import the javax.jms package 
import javax.ejb.*; 
import java.util.*; 
import java.lang.reflect.*; 
import java.rmi.RemoteException; 

import weblogic.common.*; 

 * JmsEjbReceiver.java 
 * 
 * Implements Weblogic Start-Up class that receives JMS 
 * Messages on an incoming Queue using Weblogic's JMS 
 * implementation. 
 * 
 * @author Scott Grant 
 * @version 1.0 - 4/10/00 

public class ReceiverStartup extends JmsQueueManager 
                               implements MessageListener, 
                                           T3StartupDef 
{ 
  
    // Note: These parameters could be passed in the Hashtable 
    // arguments to the Weblogic Start-Up Class via the 
    // "weblogic.properties" file...I have hard coded them here... 
    public static final String JNDI_FACTORY = 
    "weblogic.jndi.WLInitialContextFactory"; 
    public static final String URL = "t3://localhost:7001"; 
    public static final String JMS_FACTORY = 
    "javax.jms.QueueConnectionFactory"; 
    public static final String QUEUE = 
    "jdj.article.jmsejb.ejbMessageQueue"; 
    public static final String PRINCIPAL = "system"; 
    public static final String CREDENTIAL = "password"; 
  
    private T3ServicesDef serv; // Part of Weblogic Start-Up 
    class support 
  
    public ReceiverStartup() 
    { 
        super(JNDI_FACTORY, URL, QUEUE, JMS_FACTORY, PRINCI- 
        PAL, CREDENTIAL); 
    } 
  
    // Weblogic Start-Up Class 
  
    public void setServices(T3ServicesDef s) 
    { 
        serv = s; 
    } 

    * startup 
    * 
    * This is part of the Weblogic T3StartupDef 
    * interface 
    * 
    public String startup(String name, Hashtable args) throws 
    Exception 
    { 
        ctx = getInitialContext(); 
  
        if (ctx != null) 
            initializeJMS(RECEIVER, this, false);   // Not 
            using transactions 
        else 
            throw new NamingException("ReceiverStartup - 
            initializeJMS: Naming Exception was thrown"); 
  
  
        return "ReceiverStartup listening..."; 
    } 
  
    // JMS Items 
    * Implements the onMessage method of the MessageListener 
    * interface. This is the call back method used by JMS to 
    * pass us messages on the queue our which we're 
    * registered with... 
  
 public void onMessage(Message msg) 
 { 
     System.out.println("ReceiverStartup: Received incoming 
     JMS message..."); 
  
        try 
        { 
            String msgType = msg.getJMSType(); 
            if (msg instanceof MapMessage && 
            msgType.equals(JMSEJB_MESSAGE)) 
            { 
              MapMessage mapMsg = (MapMessage)msg; 
              String homeName = mapMsg.getString("HomeName"); 
              String createName = mapMsg.getString("CreateName"); 
              int createParams = mapMsg.getInt("CreateParams"); 
              String methodName = mapMsg.getString("MethodName"); 
              int methodParams = mapMsg.getInt("MethodParams"); 
  
              System.out.println("homeName: " + homeName); 
  
              if (ctx == null) 
                 ctx = getInitialContext(); 
  
              if (ctx != null) 
                { 
                   try 
                   { 
                      // Get the class type and parameters 
                      // for the EJBHome create method... 
                        System.out.println("Getting create 
                        class types..."); 
 Class[] createTypes = get  ClassTypesFromMessage((MapMessage)msg,
 createParams, "CreateParam", "CreateParamType"); 
                        System.out.println("Getting create 
                        arguments and types..."); 
 Object[] createArgs = getArgumentsFromMessage((MapMessage)msg,
 reateParams, "CreateParam", "CreateParamType"); 
  
                        // Get the class type and parame- 
                        // ters for the EJBObject (stub) 
                        // method to invoke... 
                        System.out.println("Getting method 
                        class types..."); 
  Class[] methodTypes = getClassTypesFromMessage((MapMessage)msg,
  ethodParams, "MethodParam", "MethodParamType"); 
                        System.out.println("Getting method 
                        arguments and types..."); 
  Object[] methodArgs = getArgumentsFromMessage((MapMessage)msg,
  methodParams, "MethodParam", "MethodParamType"); 
  
                        System.out.println("Looking up object class name..."); 
  
                        // Find our home class through JNDI... 
                        Object obj = ctx.lookup(homeName); 
                        Class homeClass = obj.getClass(); 
  
                        // Find the create method... 
                        Method methodCreate = 
               homeClass.getMethod(createName, createTypes); 
  
                        System.out.println("Invoking create 
                        method..."); 
  
                        // Invoke the create method... 
                        Object ejb = 
                      methodCreate.invoke(obj, createArgs); 
  
                        System.out.println("Invoking method 
                        " + methodName + "..."); 
  
                        // Find the EJB method in the JMS 
                        // Message... 
                        Method methodCall = ejb.get- 
                 Class().getMethod(methodName, methodTypes); 
  
                        // Invoke the EJB method... 
                        methodCall.invoke(ejb, methodArgs); 
  
                    } 
                    catch(InvocationTargetException e) 
                    { 
                        System.out.println("ReceiverStart- 
                 up: InvocationTargetException was thrown"); 
                    } 
                    catch(NoSuchMethodException e) 
                    { 
                        System.out.println("ReceiverStart- 
                    up: NoSuchMethodException was thrown"); 
                    } 
                    catch(IllegalAccessException e) 
                    { 
                        System.out.println("ReceiverStart- 
                   up: IllegalAccessException was thrown"); 
                    } 
                    catch(Exception e) 
                    { 
                        System.out.println("ReceiverStart- 
                            up: An exception was thrown"); 
                    } 
                } 
                else 
                    System.out.println("ReceiverStartup: 
       onMessage: Failed to obtain initial naming context"); 
            } 
        } 
        catch (JMSException e) 
        { 
            System.out.println("ReceiverStartup: onMessage: 
                              JMSException was thrown"); 
            e.printStackTrace(); 
        } 
    } 
  
    * getClassTypesFromMessage 
    * 
    * Returns an array of Class types from a MapMessage 
    * which contains a list of parameters for an EJB method call. 
  
    public Class[] getClassTypesFromMessage(MapMessage msg, 
    int params, 
        String argName, String typeName) throws JMSException 
    { 
        Class[] types = new Class[0]; 
  
        if (params > 0) 
        { 
            types = new Class[params]; 
  
            for (int i = 0; i < params; i++) 
            { 
                String type = msg.getString(typeName + i); 
                if (type.equals("String")) 
                { 
                    types[i] = String.class; 
                } 
                else if (type.equals("int")) 
                { 
                    types[i] = int.class; 
                } 
                else if (type.equals("long")) 
                { 
                    types[i] = long.class; 
                } 
                else if (type.equals("float")) 
                { 
                    types[i] = float.class; 
                } 
                else if (type.equals("double")) 
                { 
                    types[i] = double.class; 
                } 
                else if (type.equals("short")) 
                { 
                    types[i] = short.class; 
                } 
                else if (type.equals("byte")) 
                { 
                    types[i] = byte.class; 
                } 
                else if (type.equals("object")) 
                { 
                    // Extract any type of object... 
                    Object obj = msg.getObject(argName + i); 
                    types[i] = obj.getClass(); 
                    System.out.println("Object instance: " 
                    + obj.getClass().getName()); 
                } 
                else if (type.equals("message")) 
                { 
                    types[i] = Message.class; 
                } 
                else 
                    System.out.println("Unknown parameter 
                    type"); 
            } 
        } 
  
        return types; 
    } 
  
    * getArgumentsFromMessage 
    * 
    * Returns an array of Object's from a JMS MapMessage 
    * that are extracted as parameters for an EJB method 
    * call. 

    public Object[] getArgumentsFromMessage(MapMessage msg, int params, 
         String argName, String argType) throws JMSException 
    { 
        Object[] args = new Object[0]; 
  
        if (params > 0) 
        { 
            args = new Object[params]; 
  
            for (int i = 0; i < params; i++) 
            { 
                String type = msg.getString(argType + i); 
                if (type.equals("String")) 
                { 
                    String temp = msg.getString(argName + i); 
                    args[i] = temp; 
                } 
                else if (type.equals("int")) 
                { 
                    int temp = msg.getInt(argName + i); 
                    args[i] = new Integer(temp); 
                } 
                else if (type.equals("long")) 
                { 
                    long temp = msg.getLong(argName + i); 
                    args[i] = new Long(temp); 
                } 
                else if (type.equals("float")) 
                { 
                    float temp = msg.getFloat(argName + i); 
                    args[i] = new Float(temp); 
                } 
                else if (type.equals("double")) 
                { 
                    double temp = msg.getDouble(argName + i); 
                    args[i] = new Double(temp); 
                } 
                else if (type.equals("short")) 
                { 
                    short temp = msg.getShort(argName + i); 
                    args[i] = new Short(temp); 
                } 
                else if (type.equals("byte")) 
                { 
                    byte temp = msg.getByte(argName + i); 
                    args[i] = new Float(temp); 
                } 
                else if (type.equals("object")) 
                { 
                    Object obj = msg.getObject(argName + i); 
                    args[i] = obj; 
                } 
                else if (type.equals("message")) 
                { 
                    args[i] = msg; 
                } 
                // You could add other types here...Object,etc... 
                else 
                    System.out.println("Unknown parameter type"); 
            } 
        } 
  
        return args; 
    } 
} 

Listing 3 

package jdj.article.jmsejb; 
  
import javax.jms.*; 
import javax.naming.*; 
  
* JmsEjbClient 
* 
* This is our client class. It extends the base JmsQueueMan- 
* ager to handle the creation of the JMS connection, session, 
* and QueueSender. 
* 
* It creates two JMS MapMessages. The first is sent to the 
* EJB's "onMessage" method through simple delegation. The 
* second message is actually used to call a method on the EJB 
* asynchronously via a JMS message - both cases are handled 
* by the ReceiverStartup Weblogic startup class. 
* 
* @author Scott Grant 
* @version 1.0 - 4/10/00 
  
public class JmsEjbClient extends JmsQueueManager 
{ 
    public static final String JNDI_FACTORY = 
    "weblogic.jndi.WLInitialContextFactory"; 
    public static final String URL = "t3://localhost:7001"; 
    public static final String JMS_FACTORY = 
    "javax.jms.QueueConnectionFactory"; 
    public static final String QUEUE = 
    "jdj.article.jmsejb.ejbMessageQueue"; 
    public static final String PRINCIPAL = "system"; 
    public static final String CREDENTIAL = "password"; 
  
    public JmsEjbClient() 
    { 
        super(JNDI_FACTORY, URL, QUEUE, JMS_FACTORY, PRINCI 
        PAL, CREDENTIAL); 
    } 
  
    public static void main(String[] args) 
    { 
        JmsEjbClient client = new JmsEjbClient(); 
        client.initializeJMS(SENDER, null, false); 
  
        MapMessage msg = client.createMapMessage(); 
        try 
        { 
            // Create a MapMessage and send it to the Queue 
            msg.setJMSType(JMSEJB_MESSAGE); 
            msg.setString("HomeName", 
            "jdj.article.jmsejb.JmsEjbExampleHome"); 
            msg.setString("CreateName", "create"); 
            msg.setInt("CreateParams", 0); 
  
            // We are sending this method to the "onMessage" 
            // method of the EJB, so we set one parameter 
            // which is of type "message". This is a special 
            // case and the ReceiverStartup will interpret 
            // this as delegation - it will pass the actual 
            // JMS Message itself, on to this method (onMes- 
            // sage) of the EJB as the parameter. 
            msg.setString("MethodName", "onMessage"); 
            msg.setInt("MethodParams", 1); 
            msg.setString("MethodParamType0", "message"); 
            msg.setString("StringMessage", "This is my test 
       message string sent to the EJB onMessage method..."); 
  
            client.sendMessage(msg); 
  
            // Now send a method invocation with Params... 
  
            msg = client.createMapMessage(); 
            msg.setJMSType(JMSEJB_MESSAGE); 
            msg.setString("HomeName", 
            "jdj.article.jmsejb.JmsEjbExampleHome"); 
            msg.setString("CreateName", "create"); 
            msg.setInt("CreateParams", 0); 
  
            // We are sending this message to ReceiverStart- 
            // up as a method 
            // invocation. So we set the method name, and 
            // the parameter types, and values. The 
            // ReceiverStartup class will use Java's reflec- 
            // tion mechanism to find this method on the 
            // EJB, and invoke it, passing 
            // in the parameters as we've specified them. 
            // This demonstrates using a Weblogic startup 
            // class as a delegator for asynchronous 
            // method invocation on an EJB... 
            msg.setString("MethodName", "testMethod"); 
            msg.setInt("MethodParams", 2); 
            msg.setString("MethodParamType0", "int"); 
            msg.setInt("MethodParam0", 1); 
            msg.setString("MethodParamType1", "String"); 
            msg.setString("MethodParam1", "This is my test message string sent to the EJB
			 testMethod as method parameter..."); 
  
            client.sendMessage(msg); 
        } 
        catch(JMSException e) 
        { 
            System.out.println("JmsEjbClient: JMSException was thrown"); 
        } 
    } 
} 



 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.