Enterprise JavaAn Architecture for Making Asynchronous EJB Calls Using JMS
- By by Tyler Jewell
- April 18, 2000
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 }