wo major limitations in the Enterprise JavaBeans (EJB) specification make it difficult to develop multi-threaded EJB applications:
- New user threads cannot be created inside EJB Container.
- EJB methods cannot be called asynchronously.
These restrictions make it difficult to develop concurrent applications with EJB. Luckily, by utilizing JMS along with EJB within your applications, you can achieve concurrency. JMS’s asynchronous model and its support for the MessageDrivenBean (MDB) can be applied as a mechanism for a client to use EJB asynchronously.
The Need for Concurrency in EJB
Concurrent processing in EJB is a necessity for various applications. For example, say you have a database containing various commodity prices. Each commodity has more than one price, so you want to identify the minimum price. Assume that identifying a minimum price requires exhaustive calculations, which are implemented by a SessionBean. The SessionBean has a method that accepts a commodity and finds its minimum price.
Now consider a client that has a set of commodities and wants to find a minimum price for each. The client can call the SessionBean by passing one commodity at a time to find its minimum price; in which case it has to call the SessionBean serially for each commodity. Due to the restrictions in EJB, you cannot create threads within the SessionBean that will allow it to accept the commodity in a new thread and return the control back to the client, enabling it to send the next commodity. The only alternative for the client is to create multiple threads outside the SessionBean, so that each thread can call the SessionBean concurrently to find the minimum price for the Commodities. This approach forces the client to manage the threads, which is extra work.
To avoid creating multiple threads in the client, you can use JMS to send each commodity asynchronously for processing in the SessionBean. The JMS approach is also useful when communication between the client and the SessionBean is unreliable, or when the client and the SessionBean are distributed on a heterogeneous platform.
JMS Design for Concurrency
To call EJB concurrently, you can design a set of classes that a client uses to divide a task into subtasks. Then the client can send each subtask asynchronously as a message to JMS Queue for processing by the MDB. The MDB can call the SessionBean to process the subtask and then to send an acknowledgement back to the client.
The following section discusses both the client-side and server-side designs for using EJBs concurrently.
On the client side, you need two JMS Queues: one to send subtasks and the other to receive acknowledgement for the processed subtasks. Figure 1 shows the class diagram for client-side processing logic.
|Figure 1: Class Diagram for Client-side Processing Logic
The TaskManager (see Listing 1) interface has common behavior that must be implemented by any class that provides implementation for subtask management. The JMSTaskManager (see Listing 2) implements the TaskManager and provides JMS implementation for calling the EJB concurrently. For example, you could use the TaskManager to implement Web services for managing and processing subtasks.
You use three methods collectively to perform subtask management for the clients:
sendSubTasks()sends a collection of subtasks to the EJB for processing.
listenForAcknowledgement()listens for the acknowledgement messages for all the subtasks sent to MDB.
waitForAcknowledgement()makes the client wait for the acknowledgement to come from MDB.
As explained previously, the JMSTaskManager class (see Listing 2) provides JMS implementation for the TaskManager. The default constructor initializes totalMessagesSent and totalMessagesReceived variables, which hold messages (subtasks) sent and acknowledgements received, respectively, for the processed subtasks. It also constructs the InitialContext for JNDI lookup and calls
setupQueue(), which constructs JMS Queue objects to perform point-to-point communication between JMSTaskManager and the MDB.
sendSubTasks() method takes a collection of subtasks, adds each subtask into JMS message, and sends it to the JMS Server. The
createMessage() method is an abstract of this class that takes QueueSession and a subtask and then returns a message for
sendSubTasks() to send. Since
createMessage() is an abstract, subclasses can construct all types of JMS messages, such as BytesMessage, MapMessage, ObjectMessage, StreamMessage, TextMessage, etc. The constructed message is used to set the JMS Reply Queue on the server side. Once the MDB processes a subtask, it sends an acknowledgement message back to JMSTaskManager (using the Reply Queue). The name of the Reply Queue is obtained from
getReceiveQueueName(), which is an abstract method in this class. Finally, the QueueSender object is used to send the messages to the JMS Server.
sendMessage() is provided for convenience. If, for any reason, a client wants to send only one subtask, this method can be used for that.
onMessage() method is automatically executed when the MDB sends an acknowledgement message to the Reply Queue and increments the totalMessageReceived counter by one.
listenForAcknowledgement() method uses
getReceiveQueueName() to create a Queue to receive the acknowledgements from the MDB. The JMSTaskManager implements the MessageListener interface so that it can listen for acknowledgements from MDB. The QueueSession is used to create the QueueReceiver, which is used to set the MessageListener object to get acknowledgements from MDB.
You can create two types of QueueReceivers, one using MessageSelector and one without using it. If the subclass of JMSTaskManager provides a MessageSelector, you use it to construct the QueueReceiver. The JMSTaskManager uses the date as the MessageSelector to filter the acknowledgement messages from the MDB. This feature allows more than one client to use the JMSMessageManager to send subtasks concurrently to MDB (see Figure 17). In this scenario, each client uses its own date as the message Selector so that JMSTaskManager receives acknowledgement messages for its date.
waitForAcknowledgement() method waits in a loop until the JMSTaskManager receives all the acknowledgement messages from MDB.
This section discusses what the server-side classes require to process subtasks using JMS Messages. On the server side, you need to design a MDB so that when the client sends a message, the JMS Server notifies the MDB to process that message.
|Figure 2: QueueConstants Interface Holds Various Queue Constants
The JMS Server calls the
onMessage() method when the JMSTaskManager sends a message with the subtask embedded in it. Typically, this method calls a SessionBean to process the Subtask. For simplicity, in this example it prints the date and the subtask passed by the client. At the end, it calls the
sendAcknowledgement() method to send the acknowledgement message to the client.
sendAcknowledgement() method receives the Reply Queue that the JMSTaskManager sends and uses it to send the text acknowledgement message back to it. It uses the client-passed date to set the message Selector by calling the setStringProperty in the TextMessage. This helps the client accept the acknowledgement messages based on the date it passed to the SubtaskMessageBean.
The QueueConstants interface (see Listing 4) holds various Queue constants that both the client-side and server-side Java classes use.
JMS Design Implementation
Now it’s time to see how you can use the client- and server-side designs and develop a simple implementation for sending subtasks to the SubtaskMessageBean. You need to implement only the client side. For the server side, you do not need to implement any new classes.
The SimpleTaskManager class (see Listing 5) extends JMSTaskManager and provides implementation for all the abstract methods (see Figure 3). As explained previously, the
onMessage() method gets acknowledgement messages for finished subtasks from the SubtaskMessageBean, and it simply prints the TextMessage the SubtaskMessageBean sends. All other methods provide JMS Queue-specific details (such as Queue Name, Queue Factory Name, etc.). By implementing all these methods in SimpleTaskManager, you make the JMSTaskManager generic, providing default behavior for the subtask management.
|Figure 3: SimpleTaskManager Class Extends JMSTaskManager
getMessageSelector() method returns the current date as the Message Selector so that the JMSTaskManager can use it to filter the acknowledgement messages from SubTaskMessageBean.
createMessage() method creates a TextMessage, sets the String Subtask, and calls the
setStringProperty() to pass the current date as the message Selector.
The DemoClient class (see Listing 6) constructs the SimpleTaskManager and passes a collection of subtasks to the
sendSubTasks(). Then it calls
waitForAcknowledgement() to listen and wait for the acknowledgement messages from the SubtaskMessageBean.
Running the Implementation in WSAD 5.1
This section discusses how you can run the DemoClient and SubtaskMessageBean in WSAD 5.1. (Click here for more detailed discussions about setting up JMS queues and running MDBs.)
Set Up Client-side Classes
In WSAD 5.1, from a J2EE perspective:
- Choose File, New, and then Project from the pop-up (see Figure 4).
- Choose J2EE and select Application client Project.
- Press Next, choose J2EE 1.3 Application client Project, and press Next twice.
- Type the project name as DemoClient and type DemoClientEAR for EAR Project.
- Press Finish to close the pop-up.
The above steps create a DemoClient and DemoClientEAR projects in WSAD, which you can see in Project Navigator. Open the DemoClient project and select appClientModule. Right mouse click to open the pop-up and choose Import. From the Import pop-up, select Zip file (see Figure 5) and press Next. In the file location specify the location of DemoClient.zip (included in the code download for this article) and press Finish. This will import the all the client-side Java classes and place them in a com.cybelink.client package. You can see all the classes in the package com.cybelink.client in the Project Navigator window in a J2EE perspective. You will also see a red x mark on the SimpleTaskManager class.
Open SimpleTaskManager and you’ll notice the compiler complaining about missing com.cybelink.mdb.QueueConstants. You’ll resolve this when you finish importing server-side classes.
|Figure 4: WSAD 5.1 from a J2EE Perspective
|Figure 5: The Import Pop-up
Now you need to add the class name in the client Deployment Descriptor so that you can call the
main() method in that class during DemoClient startup. Open the MANIFEST.MF under META-INF in the DemoClient project (see Figure 6). Press the Browse button to select DemoClient for the Main-Class, and save the changes. These steps help WSAD 5.1 identify the class name that contains the main method.
|Figure 6: The Import Pop-up
After importing all the client-side classes, the next step is creating an application client configuration that will start the DemoClient application. From the J2EE perspective, choose Run in the Main Menu and from the sub menus choose Run again to open a pop-up. In the pop-up’s configurations options on the left panel (see Figure 7), choose WebSphere v5 Application client and press New. In the name field, change the configuration from New_configuration to DemoClient and press Apply. Make sure that DemoClientEAR is selected for the Enterprise Application and DemoClient for Application client. Select Classpath and press Add External JARs.
|Figure 7: The Pop-up’s Configurations Options
In the Jar Selection pop-up, choose the com.ibm.mq.jar and com.ibm.mqjms.jar files (see Figure 8) and press Open. These two Jar files are located in your
|Figure 8: The Pop-up’s Configurations Options
You have created a Java client project, imported the necessary Java classes, and added the necessary Jar files so that DemoClient can start within WSAD 5.1 and communicate with the WebSphere MQ. Your next step is to create a project for server-side SubtaskMessageBean so that WSAD 5.1 can start it.
Set Up Server-side Classes
Setting up server-side classes is a two-step process. First, you create a project for the server-side classes and then you create a server and server configuration to setup various JMS configurations.
Create a Project for MDB
In the J2EE perspective from the File menu, choose New and choose EJB Project from the submenus. In the pop-up window, choose Create 2.0 EJB Project and press Next (see Figure 9). Enter SimpletaskMDB for Project Name, select DemoClientEAR for EAR Project, and press Finish. These steps create a SimpletaskMDB EJB project, which you can see in the J2EE perspective.
|Figure 9: New EJB Project Pop-up Window
Open the SimpletaskMDB project, choose ejbModule, and right mouse click to open a pop-up window. From that window (see Figure 5), select Import and choose Zip File. Enter the SimpletaskMDB.zip (included in the code download for this article) location to import the SimpletaskMDB and QueueConstants Java classes into WSAD 5.1.
Now you can fix the DemoClient Project’s missing QueueConstants reference in SimpleTaskManager. Select DemoClient project and right mouse click. From the opened pop-up window, select Properties option. In the window (see Figure 10), choose the Java Build Path option on the left panel. On the right, choose the Projects tab, select the SimpletaskMDB project, and press OK. This enables the classes in the DemoClient project to access the classes in the SimpletaskMDB project. Youll see the compiler errors cleared from SimpleTaskManager.
|Figure 10: Choose the Java Build Path Option
Now you are ready to configure the SimpleTaskMessageBean in ejb-jar.xml so that it can listen for incoming messages in WebSphere MQ.
Open the ejb-jar.xml file, which is under META-INF in the project SimpletaskMDB, and then go to the Beans tab. In the Beans page, press the Add button to open the pop-up (see Figure 11), select Message-driven bean, and enter SubtaskMessage for the Bean name. Press Next to select Queue for Destination Type (see Figure 12) and enter subtaskListenerQ for ListenerPort Name. Press Finish to close the pop-up.
|Figure 11: Press the Add Button to Open the Pop-up
|Figure 12: Press Next to Select Queue for Destination Type
You have modified the ejb-jar.xml file to include the SubtaskMessageBean configuration parameters.
Server and Server Configuration Creation
In WSAD 5.1, switch to the Server perspective. From the Server window, select Servers and right mouse click to open the pop-up. From the pop-up, choose New and then choose Server and Server Configuration to open pop-up (see Figure 13).
|Figure 13: Choose Server and Server Configuration to Open Pop-up
For the Server Name type EJBConcurrency and for the Server type select Test Environment. Press Finish to close the pop-up. These steps create a new Server EJBConcurrency, which you can see in the Server Configuration window. Right mouse click the EJBConcurrency and select Add and remove Projects. From the pop-up (see Figure 14) choose DemoClientEAR from the Available Projects and press Add. Then press Finish to add DemoClientEAR to EJBConcurrency Server.
|Figure 14: Choose DemoClientEAR from the Available Projects
Your next task is to configure the EJBConcurrency server so that WebSphere MQ can start the JMS queues. In the Server Configuration open EJBConcurrency in the editor and press the JMS tab to open the WebSphere MQ configuration screen. In JMS Server Properties under Server Settings (see Figure 16), click the Add button to enter Queue Name SubtaskSendQueue. Similarly enter Queue Name SubtaskReceiveQueue. In the JMS Provider options, select Embedded Messaging. In the WASQueueConnectionFactory entries, press the Add button to open the pop-up window (see Figure 15). In the Name field, enter QConnectionFactory. For JNDI Name, enter jms/QConnectionFactory and press OK to close the pop-up.
|Figure 15: Press the Add Button to Open the Pop-up
|Figure 16: The EJBConcurrency Server After All the Changes
In the WASQueue entries, press Add to open the pop-up window. Enter SubtaskSendQueue for Name and jms/SubtaskSendQueue for the JNDI name. Similarly, add SubtaskReceiveQueue and jms/SubtaskReceiveQueue. After all these changes your server setting will look like Figure 16.
Now press the EJB tab to enter Listener Port information for the SubtaskMessageBean. Press the Add button to open the pop-up window (see Figure 17) and enter subtaskListenerQ for Name, select jms/QConnectionFactory for Connection Factory JNDI Name, and select jms/SubtaskSendQueue for Destination for JNDI Name. Leave the rest of the fields as is. Press OK to close the window and save all your configuration changes.
|Figure 17: The EJBConcurrency Server After All the Changes
After all the preceding setup steps, you are now ready to run your client and server programs in WSAD 5.1.
Run the Client and Server Programs
First you need to start the server so that when you start DemoClient it can send JMS messages to the SubtaskMessageBean.
From the Server perspective, select EJBConcurrency, right mouse click to open the pop-up, and select Start to start the EJBConcurrency Server. These steps start the SubtaskMessageBean, which listens for JMS Messages in the subtaskListenerQ port to process the subtasks the DemoClient sends.
Switch to the J2EE perspective. Open the Run menu (see Figure 7), choose DemoClient, and press Run to have the DemoClient send subtasks to SubtaskMessageBean.
The DemoClient (see Figure 18) communicates with the SimpleTaskManager, which sends the JMS messages to the SubtaskSendQueue. The SubtaskMessageBean listens in the port subtaskListenerQ for JMS messages that come into the SubtaskSendQueue. Then it retrieves the Subtask, processes it, and sends a JMS acknowledgement to SubtaskReceiveQueue. The SimpleTaskManager that sends the JMS subtask will get the acknowledgement from the SubtaskReceiveQueue and notify the DemoClient. Figure 13 shows two DemoClients started concurrently, each with its own SimpleTaskManager sending subtasks to SubtaskSendQueue and receiving acknowledgements from the SubtskReceiveQueue.
|Figure 18: The DemoClient
|Figure 19: The WSAD 5.1 Console Window for DemoClient
|Figure 20: The WSAD 5.1 Console Window for Server
Concurrent Applications with EJBs
JMS provides an elegant solution for overcoming the restrictions you face when developing concurrent applications with EJBs. The basic techniques described in client-side and server-side classes can be extended to develop various types of concurrent applications using EJB and JMS. Although this article explains how to use these classes in the context of concurrent applications, you can also use them to develop various other JMS applications. This is due to the fact that they abstract the generic behavior of connecting to JMS Queues, sending messages, and waiting for acknowledgements. Similarly, you have used WSAD 5.1 to configure and run the program, but you can also use any J2EE-based app server to run it.