|
Comments
Did you read today's front page stories & breaking news?
SYS-CON.TV
|
General Java Using the Java Messaging Service with Enterprise JavaBeans
Using the Java Messaging Service with Enterprise JavaBeans
By: Scott Grant
Jun. 1, 2000 12:00 AM
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 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
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
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.
The Solution: WebLogic Startup Classes
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.
Using the Delegation Model
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
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
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
Reader Feedback: Page 1 of 1
Latest Cloud Developer Stories
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week
Breaking Cloud Computing News |
|||||||||||||||||||||||||||||||||||||||||||||||||