View Javadoc

1   package org.lsst.ccs.bus.jms;
2   
3   import org.lsst.ccs.bus.*;
4   import org.lsst.ccs.utilities.logging.Logger;
5   
6   import javax.jms.Destination;
7   import javax.jms.JMSException;
8   import javax.jms.MessageConsumer;
9   import javax.jms.MessageListener;
10  import javax.jms.MessageProducer;
11  import javax.jms.ObjectMessage;
12  import javax.jms.Queue;
13  import javax.jms.Session;
14  import java.util.Map;
15  import java.util.concurrent.ConcurrentHashMap;
16  import java.util.concurrent.ExecutorService;
17  import java.util.concurrent.Executors;
18  
19  /**
20   * JMS implementation of the messaging factory
21   * 
22   * @author aubourg
23   * 
24   */
25  public class JMSMessagingFactory extends MessagingFactory {
26  
27  	// Thread local pattern assumes reply will be send on same thread
28  	// will not handle deferred executions of commands.
29  	// thread local context is only set when receiving a message
30  	ThreadLocal<String> localCorrelID = new ThreadLocal<String>();
31  	ThreadLocal<Destination> localReplyTo = new ThreadLocal<Destination>();
32  	ThreadLocal<String> localOrigin = new ThreadLocal<String>();
33  	ThreadLocal<Command> localCommand = new ThreadLocal<Command>();
34  	String token = java.util.UUID.randomUUID().toString(); // for arbitrator
35  
36  	// should be set to "MAIN" for the main control module that holds all locks
37  	// initially
38  	// TODO (at the moment default value for subsystem allows any command)
39  	@Override
40  	public String getToken() {
41  		return token;
42  	}
43  
44  	protected static Logger log = Logger
45  			.getLogger("org.lsst.ccs.bus.jms.JMSMessagingFactory");
46  
47  	@Override
48  	public void addCommandListener(final CommandListener l) {
49  		addCommandListener(l, null);
50  	}
51  
52  	// TODO add executor service to handle multiple commands in parallel
53  	// would process the payload
54  	ExecutorService exec = Executors.newFixedThreadPool(5); // TODO configurable
55  
56  	class MessageHandler implements Runnable {
57  
58  		public MessageHandler(ObjectMessage msg, CommandListener l) {
59  			this.msg = msg;
60  			this.l = l;
61  		}
62  
63  		ObjectMessage msg;
64  		CommandListener l;
65  
66  		public void run() {
67  			Object payload;
68  			try {
69  				payload = ((ObjectMessage) msg).getObject();
70  
71  				if (payload instanceof Command) {
72  					localCorrelID.set(msg.getJMSCorrelationID());
73  					localReplyTo.set(msg.getJMSReplyTo());
74  					localOrigin.set(((Command) payload).getOrigin());
75  					localCommand.set((Command) payload);
76  					l.onCommand((Command) payload);
77  				} else if (payload instanceof CommandReply) {
78  					localCorrelID.set(msg.getJMSCorrelationID());
79  					localReplyTo.set(null);
80  					localOrigin.set(null);
81  					l.onReply((CommandReply) payload);
82  				} else if (payload instanceof CommandAck) {
83  					localCorrelID.set(msg.getJMSCorrelationID());
84  					localReplyTo.set(null);
85  					localOrigin.set(null);
86  					l.onAck((CommandAck) payload);
87  				} else {
88  					log.warn("Message payload type "
89  							+ payload.getClass().getName() + " not handled "
90  							+ payload);
91  				}
92  			} catch (JMSException e) {
93  				log.error("Problem receiving message, class not handled?", e);
94  			}
95  
96  		}
97  	}
98  
99  	// in order to remove listeners, we need to keep track of their associated
100 	// consumer.
101 
102 	Map<CommandListener, MessageConsumer> commandListenerMap = new ConcurrentHashMap<CommandListener, MessageConsumer>();
103 
104 	@Override
105 	public void addCommandListener(final CommandListener l, String selector) {
106 		log.debug("adding command listener, selector='" + selector + "'");
107 		Session session = TopicSessionFactory.getSessionFactory()
108 				.getTopicSession();
109 		MessageConsumer sub = (selector == null) ? TopicSessionFactory
110 				.getSessionFactory().getCommandSubscriber(session)
111 				: TopicSessionFactory.getSessionFactory().getCommandSubscriber(
112 						session, selector);
113 		try {
114 			MessageListener ml = new MessageListener() {
115 
116 				public void onMessage(javax.jms.Message msg) {
117 					if (msg instanceof ObjectMessage) {
118 						log.debug("MessageListener got jms message on command");
119 
120 						MessageHandler mh = new MessageHandler(
121 								(ObjectMessage) msg, l);
122 						exec.execute(mh);
123 
124 						// Object payload;
125 						// try {
126 						// payload = ((ObjectMessage) msg).getObject();
127 						//
128 						// if (payload instanceof Command) {
129 						// localCorrelID.set(msg.getJMSCorrelationID());
130 						// localReplyTo.set(msg.getJMSReplyTo());
131 						// localOrigin
132 						// .set(((Command) payload).getOrigin());
133 						// localCommand.set((Command) payload);
134 						// l.onCommand((Command) payload);
135 						// } else if (payload instanceof CommandReply) {
136 						// localCorrelID.set(msg.getJMSCorrelationID());
137 						// localReplyTo.set(null);
138 						// localOrigin.set(null);
139 						// l.onReply((CommandReply) payload);
140 						// } else {
141 						// log.warn("Message payload type "
142 						// + payload.getClass().getName()
143 						// + " not handled " + payload);
144 						// }
145 						// } catch (JMSException e) {
146 						// throw new RuntimeException(e);
147 						// }
148 					} else {
149 						log.warn("Message  type " + msg.getClass().getName()
150 								+ " not handled " + msg);
151 
152 					}
153 				}
154 			};
155 			commandListenerMap.put(l, sub);
156 			sub.setMessageListener(ml);
157 			commandMessageListener = ml;
158 		} catch (JMSException e) {
159 			throw new RuntimeException(e);
160 		}
161 	}
162 
163 	@Override
164 	public void removeCommandListener(CommandListener l) {
165 		MessageConsumer sub = commandListenerMap.get(l);
166 		// if sub is null it's either an error from the caller or could be a
167 		// race condition, to be handled : locking, retry ?
168 		if (sub == null)
169 			return;
170 		try {
171 			sub.setMessageListener(null);
172 			sub.close();
173 		} catch (JMSException e) {
174 			throw new RuntimeException(e);
175 		}
176 		commandListenerMap.remove(l);
177 	}
178 
179 	MessageListener commandMessageListener; // TDOD
180 
181 	// We keep it because we want to use it a the target for command replies
182 	// TODO : separate cleanly commands and command replies
183 	// check what we should do with the temporary queue
184 	// TODO : check if we don't keep a unique queue for a subsystem
185 	// instead of temp queues for each commands
186 	// backup solution is to send command replies on the command topic, which
187 	// works (was version 1 still commented out).
188 	@Override
189 	public void addStatusListener(final StatusListens l) {
190 		addStatusListener(l, null);
191 	}
192 
193 	Map<StatusListens, MessageConsumer> statusListenerMap = new ConcurrentHashMap<StatusListens, MessageConsumer>();
194 
195 	@Override
196 	public void addStatusListener(final StatusListens l, String selector) {
197 		log.debug("adding status listener, selector='" + selector + "'");
198 		Session session = TopicSessionFactory.getSessionFactory()
199 				.getTopicSession();
200 		MessageConsumer sub = TopicSessionFactory.getSessionFactory()
201 				.getStatusSubscriber(session, selector);
202 		MessageListener ml = new MessageListener() {
203             //toDO: CHANGE THAT!!!!
204             BusApplicationLayer.ForwarderToStatus forwarder =
205                     new BusApplicationLayer("", null).new ForwarderToStatus(l);
206 
207 			public void onMessage(javax.jms.Message msg) {
208 				if (msg instanceof ObjectMessage) {
209 					log.debug("MessageListener got jms message on status");
210 					Object payload;
211 					try {
212 						payload = ((ObjectMessage) msg).getObject();
213 					} catch (JMSException e) {
214 						log.error(
215 								"Got Object Message, could not deserialize: missing class?",
216 								e);
217 						return;
218 						// throw new RuntimeException(e);
219 					}
220 					if (payload instanceof Status) {
221                         forwarder.update((BusMessage)payload);
222 					} else {
223 						log.warn("Message payload type "
224 								+ payload.getClass().getName()
225 								+ " not handled " + payload);
226 
227 					}
228 				} else {
229 					log.warn("Message  type " + msg.getClass().getName()
230 							+ " not handled " + msg);
231 
232 				}
233 			}
234 		};
235 		try {
236 			statusListenerMap.put(l, sub);
237 			sub.setMessageListener(ml);
238 		} catch (JMSException e) {
239 			log.error(e);
240 			throw new RuntimeException(e);
241 		}
242 	}
243 
244 	@Override
245 	public void removeStatusListener(StatusListens l) {
246 		MessageConsumer sub = statusListenerMap.get(l);
247 		// if sub is null it's either an error from the caller or could be a
248 		// race condition, to be handled : locking, retry ?
249 		if (sub == null)
250 			return;
251 		try {
252 			sub.setMessageListener(null);
253 			sub.close();
254 		} catch (JMSException e) {
255 			throw new RuntimeException(e);
256 		}
257 		statusListenerMap.remove(l);
258 	}
259 
260 	@Override
261 	public void addLogListener(LogListener l) {
262 		addLogListener(l, null);
263 	}
264 
265 	Map<LogListener, MessageConsumer> logListenerMap = new ConcurrentHashMap<LogListener, MessageConsumer>();
266 
267 	@Override
268 	public void addLogListener(final LogListener l, String selector) {
269 		log.debug("adding log listener, selector='" + selector + "'");
270 		Session session = TopicSessionFactory.getSessionFactory()
271 				.getTopicSession();
272 		MessageConsumer sub = TopicSessionFactory.getSessionFactory()
273 				.getLogSubscriber(session, selector);
274 		MessageListener ml = new MessageListener() {
275 
276 			public void onMessage(javax.jms.Message msg) {
277 				if (msg instanceof ObjectMessage) {
278 					log.debug("MessageListener got jms message on log");
279 					Object payload;
280 					try {
281 						payload = ((ObjectMessage) msg).getObject();
282 					} catch (JMSException e) {
283 						throw new RuntimeException(e);
284 					}
285 					if (payload instanceof LogEvent) {
286 						l.onLog((LogEvent) payload);
287 					} else {
288 						log.warn("Message payload type "
289 								+ payload.getClass().getName()
290 								+ " not handled " + payload);
291 
292 					}
293 				} else {
294 					log.warn("Message  type " + msg.getClass().getName()
295 							+ " not handled " + msg);
296 
297 				}
298 			}
299 		};
300 		try {
301 			logListenerMap.put(l, sub);
302 			sub.setMessageListener(ml);
303 		} catch (JMSException e) {
304 			log.error(e);
305 			throw new RuntimeException(e);
306 		}
307 
308 	}
309 
310 	@Override
311 	public void removeLogListener(LogListener l) {
312 		MessageConsumer sub = logListenerMap.get(l);
313 		// if sub is null it's either an error from the caller or could be a
314 		// race condition, to be handled : locking, retry ?
315 		if (sub == null)
316 			return;
317 		try {
318 			sub.setMessageListener(null);
319 			sub.close();
320 		} catch (JMSException e) {
321 			throw new RuntimeException(e);
322 		}
323 		logListenerMap.remove(l);
324 	}
325 
326 	@Override
327 	public void sendCommand(Command cmd) {
328 		cmd.setKey(token);
329 		// Hack added by bamade
330 		if (null == cmd.getOrigin()) {
331 			cmd.setOrigin(MessagingFactory.getInstance().getSubsystemName());
332 		}
333 		if (cmd.getCorrelId() == null) {
334 			cmd.setCorrelId(localCorrelID.get());
335 		}
336 		if (cmd.getCorrelId() == null) {
337 			// let's generate one
338 			cmd.setCorrelId(java.util.UUID.randomUUID().toString());
339 		}
340 		Session sess = TopicSessionFactory.getSessionFactory()
341 				.getTopicSession();
342 		MessageProducer send = TopicSessionFactory.getSessionFactory()
343 				.getCommandPublisher(sess);
344 		ObjectMessage m;
345 
346 		try {
347 			m = sess.createObjectMessage();
348 			m.setObject(cmd);
349 			m.setJMSType(cmd.getMessageType());
350 			m.setStringProperty("destination", cmd.getDestination());
351 			m.setJMSPriority(convertToJMSPriority(cmd.getPriorityLevel()));
352 			if (cmd.getCorrelId() != null) {
353 				m.setJMSCorrelationID(cmd.getCorrelId());
354 			}
355 			log.debug("sending command " + cmd);
356 
357 			sendMessage(m, sess, send);
358 			sess.close();
359 		} catch (JMSException e) {
360 			log.error(e);
361 			throw new RuntimeException(e);
362 		}
363 	}
364 
365 	public static int convertToJMSPriority(PriorityLevel level) {
366 		switch (level) {
367 		case DEBUG:
368 			return 0;
369 		case INFO:
370 			return 4;
371 		case OPERATION:
372 			return 5;
373 		case CRITICAL:
374 			return 9;
375 		}
376 		return 4; // should not happen
377 	}
378 
379 	@Override
380 	public void sendStatus(Status s) {
381 		// hack added by bamade
382 		if (null == s.getOrigin()) {
383 			s.setOrigin(MessagingFactory.getInstance().getSubsystemName());
384 		}
385 
386 		Session sess = TopicSessionFactory.getSessionFactory()
387 				.getTopicSession();
388 		MessageProducer send = TopicSessionFactory.getSessionFactory()
389 				.getStatusPublisher(sess);
390 		ObjectMessage m;
391 		try {
392 			m = sess.createObjectMessage();
393 			m.setObject(s);
394 			m.setJMSType(s.getMessageType()); // "lsst.status", "lsst.alarm"...
395 			m.setJMSTimestamp(System.currentTimeMillis());// or source timestamp
396 			// ??
397 			m.setJMSPriority(convertToJMSPriority(s.getPriorityLevel())); // 0 =
398 			// LOW,
399 			// 9
400 			// =
401 			// HIGH
402 			m.setStringProperty("destination", "*");
403 			log.debug("sending status " + s);
404 			sendMessage(m, sess, send);
405 			sess.close();
406 		} catch (JMSException e) {
407 			log.error(e);
408 			throw new RuntimeException(e);
409 		}
410 	}
411 
412 	@Override
413 	public void sendLogEvent(LogEvent evt) {
414 		Session sess = TopicSessionFactory.getSessionFactory()
415 				.getTopicSession();
416 		MessageProducer send = TopicSessionFactory.getSessionFactory()
417 				.getLogPublisher(sess);
418 		ObjectMessage m;
419 		try {
420 			m = sess.createObjectMessage();
421 			m.setObject(evt);
422 			m.setJMSType(evt.getMessageType());
423 			m.setStringProperty("destination", "*");
424 			m.setJMSPriority(convertToJMSPriority(evt.getPriorityLevel()));
425 			log.debug("sending log " + evt);
426 
427 			sendMessage(m, sess, send);
428 			sess.close();
429 		} catch (JMSException e) {
430 			log.error(e);
431 			throw new RuntimeException(e);
432 		}
433 
434 	}
435 
436 	protected void sendMessage(ObjectMessage m, Session sess,
437 			MessageProducer sender) {
438 		try {
439 			if (m.getJMSCorrelationID() == null) {
440 				m.setJMSCorrelationID(localCorrelID.get());
441 			}
442 			m.setJMSMessageID("0"); /* TODO handle messageid */
443 			m.setJMSPriority(4); // 0-4 normal, 4-9 expedited
444 
445 			// uncomment to send replies on the command topic
446 
447 			m.setJMSReplyTo(TopicSessionFactory.getSessionFactory()
448 					.getCommandTopic());
449 
450 			// and then comment out what follows
451 
452 			// TODO close the consumer and delete the queue when reply
453 			// is received !!
454 
455 			// create temp queues for replies could be resource-consuming
456 			// check and if yes revert to sending replies on command bus
457 			//
458 			// Session qs = QueueSessionFactory.getSessionFactory()
459 			// .getQueueSession();
460 			// Queue replyQueue = qs.createTemporaryQueue();
461 			// MessageConsumer c = qs.createConsumer(replyQueue);
462 			// c.setMessageListener(commandMessageListener);
463 			// m.setJMSReplyTo(replyQueue);
464 
465 			// TODO should we provide rather a way of waiting
466 			// for the reply by polling the reply queue ?
467 			// need for a scheme to wait for reply...
468 
469 			m.setJMSTimestamp(System.currentTimeMillis());
470 
471 			sender.send(m);
472 			sender.close();
473 		} catch (JMSException e) {
474 			log.error("Error sending message", e);
475 			throw new RuntimeException(e);
476 		} finally {
477 			try {
478 				sess.close();
479 			} catch (JMSException e) {
480 				log.error("Error closing JMS session", e);
481 				throw new RuntimeException(e);
482 			}
483 		}
484 
485 	}
486 
487 	@Override
488 	public boolean isReplyRequested() {
489 		return localReplyTo.get() != null;
490 	}
491 
492 	@Override
493 	public void noAutoReply() {
494 		localReplyTo.set(null);
495 	}
496 
497 	@Override
498 	public void reply(CommandAckOrReply reply) {
499 		Destination dest = localReplyTo.get();
500 		String destSystem = localOrigin.get();
501 
502 		if (reply.getCorrelId() == null) {
503 			reply.setCorrelId(localCorrelID.get());
504 		}
505 
506 		reply.setOriginalCommand(localCommand.get());
507 		// hack added by bamade
508 		if (null == reply.getOrigin()) {
509 			reply.setOrigin(MessagingFactory.getInstance().getSubsystemName());
510 		}
511 
512 		log.debug("sending reply " + reply + " to " + dest + "[" + destSystem
513 				+ "]");
514 
515 		// is it a queue or a topic ?
516 		// with latest jms possible to factor out part of code wih Sessions and
517 		// MessagePublishers
518 		// TODO in progress
519 
520 		if (dest instanceof Queue) {
521 			Session sess = QueueSessionFactory.getSessionFactory()
522 					.getQueueSession();
523 			MessageProducer sender = QueueSessionFactory.getSessionFactory()
524 					.getCommandReplySender(sess, dest);
525 			ObjectMessage m;
526 			try {
527 				m = sess.createObjectMessage();
528 				m.setObject(reply);
529 				if (reply instanceof CommandReply)
530 					m.setJMSType("lsst.commandreply");
531 				else
532 					m.setJMSType("lsst.commandack");
533 				m.setStringProperty("destination", destSystem);
534 				m.setJMSCorrelationID(reply.getCorrelId());
535 				sender.send(m);
536 				sender.close();
537 				sess.close();
538 			} catch (JMSException e) {
539 				log.error(e);
540 			}
541 		} else {
542 			Session sess = TopicSessionFactory.getSessionFactory()
543 					.getTopicSession();
544 			MessageProducer sender = TopicSessionFactory.getSessionFactory()
545 					.getCommandReplyPublisher(sess, dest);
546 			ObjectMessage m;
547 			try {
548 				m = sess.createObjectMessage();
549 				m.setObject(reply);
550 				if (reply instanceof CommandReply)
551 					m.setJMSType("lsst.commandreply");
552 				else
553 					m.setJMSType("lsst.commandack");
554 				m.setStringProperty("destination", destSystem);
555 				m.setJMSCorrelationID(reply.getCorrelId());
556 				sender.send(m);
557 				sender.close();
558 				sess.close();
559 			} catch (JMSException e) {
560 				log.error(e);
561 			}
562 
563 		}
564 
565 	}
566     @Override
567     public void addMembershipListener(BusMembershipListener l) {
568         throw new UnsupportedOperationException("Unsupported pending re-implementation: LSSTCCS-149");
569     }
570 
571     @Override
572     public void removeMembershipListener(BusMembershipListener l) {
573         throw new UnsupportedOperationException("Unsupported pending re-implementation: LSSTCCS-149");
574     }
575 
576 }