Enterprise JavaAn Architecture for Making Asynchronous EJB Calls Using JMS

Enterprise Java
An Architecture for Making Asynchronous EJB Calls Using JMS

by Tyler Jewell

Listing 4. AsynchMethodHandler.Java.


1  package asynch;
2  
3  import java.util.*;
4  import javax.naming.*;
5  import javax.ejb.*;
6  import javax.jms.*;
7  import java.rmi.*;
8  import java.lang.reflect.*;
9  
10  public class AsynchMethodHandler {
11  
12  	private static InitialContext ctx = null;
13  	private static QueueConnectionFactory qconFactory = null;
14  	private static QueueConnection qcon = null;
15  	private static QueueSession qsession = null;
16  	private static Queue handlerQueue = null;
17  	private static Queue responseQueue = null;
18  	private static QueueReceiver qreceiver = null;
19  	private static QueueSender qsender = null;
20  
21  	// The method handler will continue to respond
22  	// to messages until this variable is set to true.
23  	private boolean quit = false;
24  public void initJNDIAndJMS() {
25  	try {
26  
27  		Hashtable env = new Hashtable();
28  		env.put(Context.INITIAL_CONTEXT_FACTORY, 
    		"weblogic.jndi.WLInitialContextFactory");
29  		env.put(Context.PROVIDER_URL, "t3://localhost:7001");
30  		ctx = new InitialContext(env);
31  
32  		// Lookup a connection factory and create a connection.
33  		qconFactory = (QueueConnectionFactory) ctx.lookup
		  ("AsynchMethodHandlerQCF");
34  		qcon = qconFactory.createQueueConnection();
35  
36  		// Create a session that is NOT transacted.
37  		// We don't need buffered messages.
38  		qsession = qcon.createQueueSession
    		(false, Session.AUTO_ACKNOWLEDGE);
39  
40  		// Create the asynchronous destination response object.
41  		// If the object is already bound in JNDI, we'll use the one
    		// bound
42  		// there.  If not, we'll create the object and bind it for future
    		// use.
43  		String handlerQueueName = "AsynchMethodHandlerQueue";
44  		try {
45  			handlerQueue = (Queue) ctx.lookup(handlerQueueName);
46  		} catch (NamingException ne) {
47  			System.out.println("Unable to locate handler queue.");
48  			System.out.println("Adding " + handlerQueueName + 
    			" to the JNDI namespace.");
49  			handlerQueue = qsession.createQueue(handlerQueueName);
50  			ctx.bind(handlerQueueName, handlerQueue);
51  		}
52  
53  		// Create a sender and a receiver for this client.
54  		// The handlers will receive requests from the EJB to have
55  		// their methods invoked asynchronously.  This client
56  		// will invoke the method of the EJB and then take
57  		// the EJB's response and send it to the client in
58  		// another JMS message.  So, this client is a sender
59  		// and a receiver.
60  		qreceiver = qsession.createReceiver(handlerQueue);
61  
62  		// Create the message listener for this receiver.
63  		// When a handler receives a message, it must:
64  		//   1) Unpack the message.
65  		//   2) Create a queue sender to the destination contained 
    		//      within the message
66  		//   3) Invoke the respective EJB with the correct parameters.
67  		//   4) Take the EJB's response and send it to the client's 
		//	     response queue.
68  		qreceiver.setMessageListener(new MessageListener() {
69  			public void onMessage(Message msg) {
70  				try {
71  
72  					long beginTime = System.currentTimeMillis();
73  					
74  					// We must first unpack the message.  All messages
    					// sent to
75  					// this destination should be map messages
    					// following a
76  					// specific format.  Refer to the EJB method to see
    					// the
77  					// packaging of this message.
78  					ObjectMessage objMsg = null;
79  					if (msg instanceof ObjectMessage)
80  					  objMsg = (ObjectMessage) msg;
81  					else
82  						System.out.println("ACK!  Did not receive a MapMessage!");
83  
84  					// All of the items of this object are stored in a
    					// hashtable in
85  					// the object itself.
86  					Hashtable contents = (Hashtable) 
						objMsg.getObject();
87  					
88  					// A message sent to this destination has many
    					// pieces embedded
89  					// within it:
90  					//   1) The destination for the response sent to the
    					//	     client.
91  					//   2) A reference to the EJB we are supposed to
    					//	     invoke.
92  					//   3) The name of the method to invoke.
93  					//   4) The types of the parameters for the method.
94  					//   5) The actual parameters.
95  					Destination clientsResponseDestination = 
96  					(Destination)contents.get("ResponseDestination");
97  					
98  					// Generate an EJBObject reference from the handle.
99  					Handle ejbHandleToInvoke = (Handle)contents.get("EJBToInvoke");
100  					EJBObject ejbToInvoke = ejbHandleToInvoke.getEJBObject();
101  
102  					boolean isStateless = 
103  					((Boolean)contents.get("HasStatelessBehavior")).
    					booleanValue();
104  
105  					// If the EJB is declared to have stateless behavior
    					// by its

106  					// author (as set in the passed in flag), then we can
    					// create
107  					// a separate instance of the EJB to make our
    					// invocation on.
108  					// If the EJB does not have stateless behavior, we'll
    					// have to
109  					// use the instance that was passed as an input
    					// parameter to make
110  					// the invocation.  We don't want to do this if we
    					// don't have to
111  					// because this thread will lock that EJB and prevent
    					// its client
112  					// from using it.  By creating a separate instance of
    					// the EJB,
113  					// we will leave the original EJB available for use.
114  					if (isStateless) {
115  						EJBHome ejbHomeToInvoke = ejbToInvoke.getEJBHome();
116  						Class classOfEJBHome = ejbHomeToInvoke.getClass();
117  						Method homeMethod = classOfEJBHome.getMethod("create", new 
118  						Class[]{});
119  						ejbToInvoke = (EJBObject)homeMethod.invoke( ejbHomeToInvoke, new 
120 				 		Object[]{});
121  					}
122  
123  					// We have to generate a java.lang.reflect.Method
    					// instance so
124  					// that we can dynamically invoke the EJB's method
    					// remotely.  The
125  					// information that we need to invoke the EJB is
    					// contained within
126  					// the message.
127  					Class classOfEJB = ejbToInvoke.getClass();
128  					String methodName = 
    					(String)contents.get("MethodNameToInvoke");
129  					int numParams = 
130  					Integer.parseInt((String)contents.get
    					("NumMethodParameters"));
131  					
132  					Object params[] = new Object[numParams];
133  					Class paramTypes[] = new Class[numParams];
134  					
135  					for (int i=1; i<=numParams; i++) {
136  						params[i-1] = contents.get("Param" + i);
137  						paramTypes[i-1] = 
								  (Class) contents.get("Param" + i + "Class");
138  					}
139  
140    					// We have to set the last parameter to null.  Why?
141  					// Well, all of the methods that we are calling are
    					// asynchronous
142  					// EJB calls.  Per our specification, all Asynchronous
    					// EJB calls
143  					// have their last parameter be a Destination object.
    					// If that value
144  					// is not null, then the EJB requests an
    					// asynchronous call. If the
145  					// value is null, then the call will be invoked
    					// synchronously.  We
146  					// only want the EJB to be invoked synchronously
    					// this time (after
147  					// all we are the handler).  So, we want to make sure
    					// the last
148  					// parameter is null!
149  					params[numParams-1] = null;
150  					
151  					Method method = classOfEJB.getMethod
							  (methodName, paramTypes);
152  					Object result = method.invoke(ejbToInvoke, params);
153  
154  					// Now that we have successfully called the EJB
    					// method synchronously,
155  					// We need to package up the return value and the
    					// execution
156  					// time into a response message to be sent to the
    					// client.
157  					Queue responseQueue = null;
158  					if (clientsResponseDestination instanceof Queue) {
159  						responseQueue = 
						 (Queue)clientsResponseDestination;
160  					} else {
161  						System.out.println("A handler cannot respond in the Pub/Sub
162  					domain!");
163  						System.exit(1);
164  					}
165  					
166  					// Many individuals might be inclined to use the
    					// same session that
167  					// was declared above.  You cannot do this.  Session
    					// objects can
168  					// only be used by a single thread.  It turns out that
    					// the thread
169  					// that calls this onMessage handler is probably a
    					// different thread
170  					// than the one that created the original session
    					// that we are
171  					// receiving message on.  If we want to send and
    					// receive messages
172  					// in parallel, we have to create a separate session
    					// for sending.
173  					// The purpose of JMS sessions is to give you
    					// parallel behavior, so
174  					// creating additional sessions is the appropriate
    					// technique here.
175  				 	QueueSession shortTimer = 
							  qcon.createQueueSession(false, 
176  					Session.AUTO_ACKNOWLEDGE);
177  					qsender = shortTimer.createSender(responseQueue);
178  
179  					// We need to send the following to the client:
180  					//   1)  Which object we worked on
181  					//   2)  The name of the method invoked
182  					//   2a) The method parameters and their types
183  					//       The client needs to know which invocation it
    					//	     made!
184  					//   3)  The class type of the return value of the
    					//	     method
185  					//   4)  The actual return value
186  					//   5)  The time in milliseconds it took to perform the invocation
187  					long executionTime = System.currentTimeMillis() - beginTime;
188  					ObjectMessage responseMsg = qsession.createObjectMessage();
189  					Hashtable objectsToSendToClient = new Hashtable();
190  					objectsToSendToClient.put("EJBInvoked", 
    					ejbHandleToInvoke);
191  					objectsToSendToClient.put("MethodInvoked", methodName);
192  
193  					// We'll just use the parameters that were sent to us
    					// from the EJB.
194  					objectsToSendToClient.put("NumMethodParameters", 
195 					contents.get("NumMethodParameters"));
196  					for (int i=1; i<=numParams; i++) {
197  					objectsToSendToClient.put(
    					"Param"+i,contents.get("Param"+i));
198  						objectsToSendToClient.put("Param"+i+"Class",
199  						contents.get("Param"+i+"Class"));
200  					}
201  
202  
203  					// There is a special case for methods that return
    					// void.
204  					// If a method returns void, the Method.invoke()
    					// method will
205  					// return null.  We need to check to see if the return
    					// value
206  					// is null and if it is, return an empty string as the
    					// return
207  					// result to the client.
208  					if (method.getReturnType().getName().
							 equalsIgnoreCase("void")) {
209  						objectsToSendToClient.put("ResultObject", "");
210    					objectsToSendToClient.put("ResultClass", "void");
211  					}
212  					else {
213  						objectsToSendToClient.put
    					("ResultObject", result);
214  					objectsToSendToClient.put("ResultClass", 
215  					method.getReturnType());
216  					}
217  					objectsToSendToClient.put("ExecutionTime",
218  					Long.toString(executionTime));
219  					responseMsg.setObject(objectsToSendToClient);
220  					
221  					qsender.send(responseMsg);
222  					
223  				} catch (Exception e) {
224  					System.out.println("JMS or JNDI Exception received." + e);
225  					e.printStackTrace();
226  				}
227  				
228  				/*
229  				AsynchSessionHome home = 
						  (AsynchSessionHome) ctx.lookup("AsynchSessionEJB");
230  		    AsynchSession ejb = (AsynchSession)home.create();
231  		    ejb.methodA(responseQueue);
232  		    */
233  		    System.out.print("+");
234  			}
235  		});
236  		qcon.start();
237  
238  	}  catch (Exception e) {
239  	   System.out.println("Error with JNDI or JMS connection.");
240  	   System.out.println(e);
241  	}
242  	
243  }
244  public static void main(String args[]) {
245  	try {
246  		AsynchMethodHandler handler = new AsynchMethodHandler();
247  		handler.initJNDIAndJMS();
248  		
249  		// Wait until a "quit" message has been received.
250  		synchronized (handler) {
251  			while (!handler.quit) {
252  				try {
253  				    handler.wait();
254  				} catch (InterruptedException ie) {
255  				}
256  			}
257  		}
258  	} catch(Exception e) {
259  	  System.out.println(e);
260  	}
261  
262  
263  }
264 }