001 /**
002 * GRANITE DATA SERVICES
003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 * This file is part of the Granite Data Services Platform.
006 *
007 * Granite Data Services is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * Granite Data Services is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 * General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 * USA, or see <http://www.gnu.org/licenses/>.
021 */
022 package org.granite.gravity.adapters;
023
024 import java.io.ByteArrayInputStream;
025 import java.io.ByteArrayOutputStream;
026 import java.io.IOException;
027 import java.io.Serializable;
028 import java.util.Date;
029 import java.util.Enumeration;
030 import java.util.HashMap;
031 import java.util.Map;
032 import java.util.Properties;
033 import java.util.Timer;
034 import java.util.TimerTask;
035
036 import javax.jms.ConnectionFactory;
037 import javax.jms.Destination;
038 import javax.jms.ExceptionListener;
039 import javax.jms.JMSException;
040 import javax.jms.MessageListener;
041 import javax.jms.ObjectMessage;
042 import javax.jms.Session;
043 import javax.jms.TextMessage;
044 import javax.naming.Context;
045 import javax.naming.InitialContext;
046 import javax.naming.NamingException;
047
048 import org.granite.clustering.DistributedDataFactory;
049 import org.granite.clustering.TransientReference;
050 import org.granite.context.GraniteContext;
051 import org.granite.gravity.Channel;
052 import org.granite.gravity.Gravity;
053 import org.granite.gravity.MessageReceivingException;
054 import org.granite.logging.Logger;
055 import org.granite.messaging.amf.io.AMF3Deserializer;
056 import org.granite.messaging.amf.io.AMF3Serializer;
057 import org.granite.messaging.service.ServiceException;
058 import org.granite.messaging.webapp.ServletGraniteContext;
059 import org.granite.util.XMap;
060
061 import flex.messaging.messages.AcknowledgeMessage;
062 import flex.messaging.messages.AsyncMessage;
063 import flex.messaging.messages.CommandMessage;
064 import flex.messaging.messages.ErrorMessage;
065
066 /**
067 * @author William DRAI
068 */
069 public class JMSServiceAdapter extends ServiceAdapter {
070
071 private static final Logger log = Logger.getLogger(JMSServiceAdapter.class);
072
073 public static final long DEFAULT_FAILOVER_RETRY_INTERVAL = 1000L;
074 public static final long DEFAULT_RECONNECT_RETRY_INTERVAL = 20000L;
075 public static final int DEFAULT_FAILOVER_RETRY_COUNT = 4;
076
077 protected ConnectionFactory jmsConnectionFactory = null;
078 protected javax.jms.Destination jmsDestination = null;
079 protected Map<String, JMSClient> jmsClients = new HashMap<String, JMSClient>();
080 protected String destinationName = null;
081 protected boolean textMessages = false;
082 protected boolean transactedSessions = false;
083 protected int acknowledgeMode = Session.AUTO_ACKNOWLEDGE;
084 protected int messagePriority = javax.jms.Message.DEFAULT_PRIORITY;
085 protected int deliveryMode = javax.jms.Message.DEFAULT_DELIVERY_MODE;
086 protected boolean noLocal = false;
087 protected boolean sessionSelector = false;
088
089 protected long failoverRetryInterval = DEFAULT_FAILOVER_RETRY_INTERVAL;
090 protected int failoverRetryCount = DEFAULT_FAILOVER_RETRY_COUNT;
091 protected long reconnectRetryInterval = DEFAULT_RECONNECT_RETRY_INTERVAL;
092
093 @Override
094 public void configure(XMap adapterProperties, XMap destinationProperties) throws ServiceException {
095 super.configure(adapterProperties, destinationProperties);
096
097 log.info("Using JMS configuration: %s", destinationProperties.getOne("jms"));
098
099 destinationName = destinationProperties.get("jms/destination-name");
100
101 if (Boolean.TRUE.toString().equals(destinationProperties.get("jms/transacted-sessions")))
102 transactedSessions = true;
103
104 String ackMode = destinationProperties.get("jms/acknowledge-mode");
105 if ("AUTO_ACKNOWLEDGE".equals(ackMode))
106 acknowledgeMode = Session.AUTO_ACKNOWLEDGE;
107 else if ("CLIENT_ACKNOWLEDGE".equals(ackMode))
108 acknowledgeMode = Session.CLIENT_ACKNOWLEDGE;
109 else if ("DUPS_OK_ACKNOWLEDGE".equals(ackMode))
110 acknowledgeMode = Session.DUPS_OK_ACKNOWLEDGE;
111 else if (ackMode != null)
112 log.warn("Unsupported acknowledge mode: %s (using default AUTO_ACKNOWLEDGE)", ackMode);
113
114 if ("javax.jms.TextMessage".equals(destinationProperties.get("jms/message-type")))
115 textMessages = true;
116
117 if (Boolean.TRUE.toString().equals(destinationProperties.get("jms/no-local")))
118 noLocal = true;
119
120 if (Boolean.TRUE.toString().equals(destinationProperties.get("session-selector")))
121 sessionSelector = true;
122
123 failoverRetryInterval = destinationProperties.get("jms/failover-retry-interval", Long.TYPE, DEFAULT_FAILOVER_RETRY_INTERVAL);
124 if (failoverRetryInterval <= 0) {
125 log.warn("Illegal failover retry interval: %d (using default %d)", failoverRetryInterval, DEFAULT_FAILOVER_RETRY_INTERVAL);
126 failoverRetryInterval = DEFAULT_FAILOVER_RETRY_INTERVAL;
127 }
128
129 failoverRetryCount = destinationProperties.get("jms/failover-retry-count", Integer.TYPE, DEFAULT_FAILOVER_RETRY_COUNT);
130 if (failoverRetryCount <= 0) {
131 log.warn("Illegal failover retry count: %s (using default %d)", failoverRetryCount, DEFAULT_FAILOVER_RETRY_COUNT);
132 failoverRetryCount = DEFAULT_FAILOVER_RETRY_COUNT;
133 }
134
135 reconnectRetryInterval = destinationProperties.get("jms/reconnect-retry-interval", Long.TYPE, DEFAULT_RECONNECT_RETRY_INTERVAL);
136 if (reconnectRetryInterval <= 0) {
137 log.warn("Illegal reconnect retry interval: %d (using default %d)", reconnectRetryInterval, DEFAULT_RECONNECT_RETRY_INTERVAL);
138 reconnectRetryInterval = DEFAULT_RECONNECT_RETRY_INTERVAL;
139 }
140
141 Properties environment = new Properties();
142 for (XMap property : destinationProperties.getAll("jms/initial-context-environment/property")) {
143 String name = property.get("name");
144 String value = property.get("value");
145
146 if ("Context.PROVIDER_URL".equals(name))
147 environment.put(Context.PROVIDER_URL, value);
148 else if ("Context.INITIAL_CONTEXT_FACTORY".equals(name))
149 environment.put(Context.INITIAL_CONTEXT_FACTORY, value);
150 else if ("Context.URL_PKG_PREFIXES".equals(name))
151 environment.put(Context.URL_PKG_PREFIXES, value);
152 else if ("Context.SECURITY_PRINCIPAL".equals(name))
153 environment.put(Context.SECURITY_PRINCIPAL, value);
154 else if ("Context.SECURITY_CREDENTIALS".equals(name))
155 environment.put(Context.SECURITY_CREDENTIALS, value);
156 else
157 log.warn("Unknown InitialContext property: %s (ignored)", name);
158 }
159
160 InitialContext initialContext = null;
161 try {
162 initialContext = new InitialContext(environment.size() > 0 ? environment : null);
163 }
164 catch (NamingException e) {
165 log.error(e, "Could not initialize JNDI context");
166 throw new ServiceException("Error configuring JMS Adapter", e);
167 }
168
169 String cfJndiName = destinationProperties.get("jms/connection-factory");
170 try {
171 jmsConnectionFactory = (ConnectionFactory)initialContext.lookup(cfJndiName);
172 }
173 catch (NamingException e) {
174 log.error(e, "Could not find JMS ConnectionFactory named %s in JNDI", cfJndiName);
175 throw new ServiceException("Error configuring JMS Adapter", e);
176 }
177
178 String dsJndiName = destinationProperties.get("jms/destination-jndi-name");
179 try {
180 jmsDestination = (Destination)initialContext.lookup(dsJndiName);
181 }
182 catch (NamingException e) {
183 log.error(e, "Could not find JMS destination named %s in JNDI", dsJndiName);
184 throw new ServiceException("Error configuring JMS Adapter", e);
185 }
186 }
187
188 protected javax.jms.Destination getProducerDestination(String topic) {
189 return jmsDestination;
190 }
191
192 protected javax.jms.Destination getConsumerDestination(String topic) {
193 return jmsDestination;
194 }
195
196 @Override
197 public void start() throws ServiceException {
198 super.start();
199 }
200
201 @Override
202 public void stop() throws ServiceException {
203 super.stop();
204
205 for (JMSClient jmsClient : jmsClients.values()) {
206 try {
207 jmsClient.close();
208 }
209 catch (Exception e) {
210 log.warn(e, "Could not close JMSClient: %s", jmsClient);
211 }
212 }
213 jmsClients.clear();
214 }
215
216
217 private synchronized JMSClient connectJMSClient(Channel client, String destination) throws Exception {
218 JMSClient jmsClient = jmsClients.get(client.getId());
219 if (jmsClient == null) {
220 jmsClient = new JMSClientImpl(client);
221 jmsClient.connect();
222 jmsClients.put(client.getId(), jmsClient);
223 if (sessionSelector && GraniteContext.getCurrentInstance() instanceof ServletGraniteContext)
224 ((ServletGraniteContext)GraniteContext.getCurrentInstance()).getSessionMap().put(JMSClient.JMSCLIENT_KEY_PREFIX + destination, jmsClient);
225 log.debug("JMS client connected for channel " + client.getId());
226 }
227 return jmsClient;
228 }
229
230 private synchronized void closeJMSClientIfNecessary(Channel channel, String destination) throws Exception {
231 JMSClient jmsClient = jmsClients.get(channel.getId());
232 if (jmsClient != null && !jmsClient.hasActiveConsumer()) {
233 jmsClient.close();
234 jmsClients.remove(channel.getId());
235 if (sessionSelector && GraniteContext.getCurrentInstance() instanceof ServletGraniteContext)
236 ((ServletGraniteContext)GraniteContext.getCurrentInstance()).getSessionMap().remove(JMSClient.JMSCLIENT_KEY_PREFIX + destination);
237 log.debug("JMS client closed for channel " + channel.getId());
238 }
239 }
240
241 @Override
242 public Object invoke(Channel fromClient, AsyncMessage message) {
243 String topicId = (String)message.getHeader(AsyncMessage.SUBTOPIC_HEADER);
244
245 if (getSecurityPolicy().canPublish(fromClient, topicId, message)) {
246 try {
247 JMSClient jmsClient = connectJMSClient(fromClient, message.getDestination());
248 jmsClient.send(message);
249
250 AsyncMessage reply = new AcknowledgeMessage(message);
251 reply.setMessageId(message.getMessageId());
252
253 return reply;
254 }
255 catch (Exception e) {
256 log.error(e, "Error sending message");
257 ErrorMessage error = new ErrorMessage(message, null);
258 error.setFaultString("JMS Adapter error " + e.getMessage());
259
260 return error;
261 }
262 }
263
264 log.debug("Channel %s tried to publish a message to topic %s", fromClient, topicId);
265 ErrorMessage error = new ErrorMessage(message, null);
266 error.setFaultString("Server.Publish.Denied");
267 return error;
268 }
269
270 @Override
271 public Object manage(Channel fromChannel, CommandMessage message) {
272 String topicId = (String)message.getHeader(AsyncMessage.SUBTOPIC_HEADER);
273
274 if (message.getOperation() == CommandMessage.SUBSCRIBE_OPERATION) {
275 if (getSecurityPolicy().canSubscribe(fromChannel, topicId, message)) {
276 try {
277 JMSClient jmsClient = connectJMSClient(fromChannel, message.getDestination());
278 jmsClient.subscribe(message);
279
280 AsyncMessage reply = new AcknowledgeMessage(message);
281 return reply;
282 }
283 catch (Exception e) {
284 throw new RuntimeException("JMSAdapter subscribe error on topic: " + message, e);
285 }
286 }
287
288 log.debug("Channel %s tried to subscribe to topic %s", fromChannel, topicId);
289 ErrorMessage error = new ErrorMessage(message, null);
290 error.setFaultString("Server.Subscribe.Denied");
291 return error;
292 }
293 else if (message.getOperation() == CommandMessage.UNSUBSCRIBE_OPERATION) {
294 try {
295 JMSClient jmsClient = connectJMSClient(fromChannel, message.getDestination());
296 jmsClient.unsubscribe(message);
297 closeJMSClientIfNecessary(fromChannel, message.getDestination());
298
299 AsyncMessage reply = new AcknowledgeMessage(message);
300 return reply;
301 }
302 catch (Exception e) {
303 throw new RuntimeException("JMSAdapter unsubscribe error on topic: " + message, e);
304 }
305 }
306
307 return null;
308 }
309
310
311 @TransientReference
312 private class JMSClientImpl implements JMSClient {
313
314 private Channel channel = null;
315 private String topic = null;
316 private javax.jms.Connection jmsConnection = null;
317 private javax.jms.Session jmsProducerSession = null;
318 private javax.jms.MessageProducer jmsProducer = null;
319 private Map<String, JMSConsumer> consumers = new HashMap<String, JMSConsumer>();
320 private boolean useGlassFishNoExceptionListenerWorkaround = false;
321 private boolean useGlassFishNoCommitWorkaround = false;
322
323 private ExceptionListener connectionExceptionListener = new ConnectionExceptionListener();
324
325 private class ConnectionExceptionListener implements ExceptionListener {
326
327 public void onException(JMSException ex) {
328 // Connection failure, force reconnection of the producer on next send
329 jmsProducer = null;
330 for (JMSConsumer consumer : consumers.values())
331 consumer.reset();
332 consumers.clear();
333 jmsConnection = null;
334 jmsProducerSession = null;
335 }
336 }
337
338
339 public JMSClientImpl(Channel channel) {
340 this.channel = channel;
341 }
342
343 public boolean hasActiveConsumer() {
344 return consumers != null && !consumers.isEmpty();
345 }
346
347
348 public void connect() throws ServiceException {
349 if (jmsConnection != null)
350 return;
351
352 try {
353 jmsConnection = jmsConnectionFactory.createConnection();
354 if (!useGlassFishNoExceptionListenerWorkaround) {
355 try {
356 jmsConnection.setExceptionListener(connectionExceptionListener);
357 }
358 catch (JMSException e) {
359 if (e.getMessage().startsWith("MQJMSRA_DC2001: Unsupported:setExceptionListener()"))
360 useGlassFishNoExceptionListenerWorkaround = true;
361 else
362 throw e;
363 }
364 }
365 jmsConnection.start();
366 log.debug("JMS client connected for channel " + channel.getId());
367 }
368 catch (JMSException e) {
369 throw new ServiceException("JMS Initialize error", e);
370 }
371 }
372
373 public void close() throws ServiceException {
374 try {
375 if (jmsProducer != null)
376 jmsProducer.close();
377 }
378 catch (JMSException e) {
379 log.error(e, "Could not close JMS Producer for channel " + channel.getId());
380 }
381 finally {
382 try {
383 if (jmsProducerSession != null)
384 jmsProducerSession.close();
385 }
386 catch (JMSException e) {
387 log.error(e, "Could not close JMS Producer Session for channel " + channel.getId());
388 }
389 }
390 for (JMSConsumer consumer : consumers.values()) {
391 try {
392 consumer.close();
393 }
394 catch (JMSException e) {
395 log.error(e, "Could not close JMS Consumer " + consumer.subscriptionId + " for channel " + channel.getId());
396 }
397 }
398 try {
399 jmsConnection.stop();
400 }
401 catch (JMSException e) {
402 log.debug(e, "Could not stop JMS Connection for channel " + channel.getId());
403 }
404 finally {
405 try {
406 jmsConnection.close();
407 }
408 catch (JMSException e) {
409 throw new ServiceException("JMS Stop error", e);
410 }
411 finally {
412 consumers.clear();
413 }
414 }
415 }
416
417 private void createProducer(String topic) throws Exception {
418 try {
419 // When failing over, JMS can be in a temporary illegal state. Give it some time to recover.
420 int retryCount = failoverRetryCount;
421 do {
422 try {
423 jmsProducer = jmsProducerSession.createProducer(getProducerDestination(topic != null ? topic : this.topic));
424 if (retryCount < failoverRetryCount) // We come from a failover, try to recover session
425 jmsProducerSession.recover();
426 break;
427 }
428 catch (Exception e) {
429 if (retryCount <= 0)
430 throw e;
431
432 if (log.isDebugEnabled())
433 log.debug(e, "Could not create JMS Producer (retrying %d time)", retryCount);
434 else
435 log.info("Could not create JMS Producer (retrying %d time)", retryCount);
436
437 try {
438 Thread.sleep(failoverRetryInterval);
439 }
440 catch (Exception f) {
441 throw new ServiceException("Could not sleep when retrying to create JMS Producer", f.getMessage(), e);
442 }
443 }
444 }
445 while (retryCount-- > 0);
446
447 jmsProducer.setPriority(messagePriority);
448 jmsProducer.setDeliveryMode(deliveryMode);
449 log.debug("Created JMS Producer for channel %s", channel.getId());
450 }
451 catch (JMSException e) {
452 jmsProducerSession.close();
453 jmsProducerSession = null;
454 throw e;
455 }
456 }
457
458 public void send(AsyncMessage message) throws Exception {
459 Object msg = null;
460 if (Boolean.TRUE.equals(message.getHeader(Gravity.BYTEARRAY_BODY_HEADER))) {
461 byte[] byteArray = (byte[])message.getBody();
462 ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
463 AMF3Deserializer deser = new AMF3Deserializer(bais);
464 msg = deser.readObject();
465 deser.close(); // makes jdk7 happy (Resource leak: 'deser' is never closed)...
466 }
467 else
468 msg = message.getBody();
469
470 internalSend(message.getHeaders(), msg, message.getMessageId(), message.getCorrelationId(), message.getTimestamp(), message.getTimeToLive());
471 }
472
473 public void send(Map<String, ?> params, Object msg, long timeToLive) throws Exception {
474 internalSend(params, msg, null, null, new Date().getTime(), timeToLive);
475 }
476
477 public void internalSend(Map<String, ?> headers, Object msg, String messageId, String correlationId, long timestamp, long timeToLive) throws Exception {
478 String topic = (String)headers.get(AsyncMessage.SUBTOPIC_HEADER);
479
480 if (jmsProducerSession == null) {
481 jmsProducerSession = jmsConnection.createSession(transactedSessions, acknowledgeMode);
482 log.debug("Created JMS Producer Session for channel %s (transacted: %s, ack: %s)", channel.getId(), transactedSessions, acknowledgeMode);
483 }
484
485 if (jmsProducer == null)
486 createProducer(topic);
487
488 javax.jms.Message jmsMessage = null;
489 if (textMessages)
490 jmsMessage = jmsProducerSession.createTextMessage(msg.toString());
491 else
492 jmsMessage = jmsProducerSession.createObjectMessage((Serializable)msg);
493
494 jmsMessage.setJMSMessageID(normalizeJMSMessageID(messageId));
495 jmsMessage.setJMSCorrelationID(normalizeJMSMessageID(correlationId));
496 jmsMessage.setJMSTimestamp(timestamp);
497 jmsMessage.setJMSExpiration(timeToLive);
498
499 for (Map.Entry<String, ?> me : headers.entrySet()) {
500 if ("JMSType".equals(me.getKey())) {
501 if (me.getValue() instanceof String)
502 jmsMessage.setJMSType((String)me.getValue());
503 }
504 else if ("JMSPriority".equals(me.getKey())) {
505 if (me.getValue() instanceof Integer)
506 jmsMessage.setJMSPriority(((Integer)me.getValue()).intValue());
507 }
508 else if (me.getValue() instanceof String)
509 jmsMessage.setStringProperty(me.getKey(), (String)me.getValue());
510 else if (me.getValue() instanceof Boolean)
511 jmsMessage.setBooleanProperty(me.getKey(), ((Boolean)me.getValue()).booleanValue());
512 else if (me.getValue() instanceof Integer)
513 jmsMessage.setIntProperty(me.getKey(), ((Integer)me.getValue()).intValue());
514 else if (me.getValue() instanceof Long)
515 jmsMessage.setLongProperty(me.getKey(), ((Long)me.getValue()).longValue());
516 else if (me.getValue() instanceof Double)
517 jmsMessage.setDoubleProperty(me.getKey(), ((Double)me.getValue()).doubleValue());
518 else
519 jmsMessage.setObjectProperty(me.getKey(), me.getValue());
520 }
521
522 jmsProducer.send(jmsMessage);
523
524 if (transactedSessions && !useGlassFishNoCommitWorkaround) {
525 // If we are in a container-managed transaction (data dispatch from an EJB interceptor for ex.), we should not commit the session
526 // but the behaviour is different between JBoss and GlassFish
527 try {
528 jmsProducerSession.commit();
529 }
530 catch (JMSException e) {
531 if (e.getMessage() != null && e.getMessage().startsWith("MQJMSRA_DS4001"))
532 useGlassFishNoCommitWorkaround = true;
533 else
534 log.error(e, "Could not commit JMS Session for channel %s", channel.getId());
535 }
536 }
537 }
538
539 private String normalizeJMSMessageID(String messageId) {
540 if (messageId != null && !messageId.startsWith("ID:"))
541 messageId = "ID:" + messageId;
542 return messageId;
543 }
544
545 public void subscribe(CommandMessage message) throws Exception {
546 String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
547 String selector = (String)message.getHeader(CommandMessage.SELECTOR_HEADER);
548 this.topic = (String)message.getHeader(AsyncMessage.SUBTOPIC_HEADER);
549
550 internalSubscribe(subscriptionId, selector, message.getDestination(), this.topic);
551 }
552
553 public void subscribe(String selector, String destination, String topic) throws Exception {
554 DistributedDataFactory distributedDataFactory = GraniteContext.getCurrentInstance().getGraniteConfig().getDistributedDataFactory();
555 String subscriptionId = distributedDataFactory.getInstance().getDestinationSubscriptionId(destination);
556 if (subscriptionId != null)
557 internalSubscribe(subscriptionId, selector, destination, topic);
558 }
559
560 private void internalSubscribe(String subscriptionId, String selector, String destination, String topic) throws Exception {
561 synchronized (consumers) {
562 JMSConsumer consumer = consumers.get(subscriptionId);
563 if (consumer == null) {
564 consumer = new JMSConsumer(subscriptionId, selector, noLocal);
565 consumer.connect(selector);
566 consumers.put(subscriptionId, consumer);
567 }
568 else
569 consumer.setSelector(selector);
570 channel.addSubscription(destination, topic, subscriptionId, false);
571 }
572 }
573
574 public void unsubscribe(CommandMessage message) throws Exception {
575 String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
576
577 synchronized (consumers) {
578 JMSConsumer consumer = consumers.get(subscriptionId);
579 try {
580 if (consumer != null)
581 consumer.close();
582 }
583 finally {
584 consumers.remove(subscriptionId);
585 channel.removeSubscription(subscriptionId);
586 }
587 }
588 }
589
590
591 private class JMSConsumer implements MessageListener {
592
593 private String subscriptionId = null;
594 private javax.jms.Session jmsConsumerSession = null;
595 private javax.jms.MessageConsumer jmsConsumer = null;
596 private boolean noLocal = false;
597 private String selector = null;
598 private boolean useJBossTCCLDeserializationWorkaround = false;
599 private boolean useGlassFishNoCommitWorkaround = false;
600 private boolean reconnected = false;
601 private Timer reconnectTimer = null;
602
603 public JMSConsumer(String subscriptionId, String selector, boolean noLocal) throws Exception {
604 this.subscriptionId = subscriptionId;
605 this.noLocal = noLocal;
606 this.selector = selector;
607 }
608
609 public void connect(String selector) throws Exception {
610 if (jmsConsumerSession != null)
611 return;
612
613 this.selector = selector;
614
615 // Reconnect to the JMS provider in case no producer has already done it
616 JMSClientImpl.this.connect();
617
618 jmsConsumerSession = jmsConnection.createSession(transactedSessions, acknowledgeMode);
619 if (reconnected)
620 jmsConsumerSession.recover();
621 log.debug("Created JMS Consumer Session for channel %s (transacted: %s, ack: %s)", channel.getId(), transactedSessions, acknowledgeMode);
622
623 if (reconnectTimer != null)
624 reconnectTimer.cancel();
625
626 try {
627 // When failing over, JMS can be in a temporary illegal state. Give it some time to recover.
628 int retryCount = failoverRetryCount;
629 do {
630 try {
631 jmsConsumer = jmsConsumerSession.createConsumer(getConsumerDestination(topic), selector, noLocal);
632 if (retryCount < failoverRetryCount) // We come from a failover, try to recover session
633 reconnected = true;
634 break;
635 }
636 catch (Exception e) {
637 if (retryCount <= 0)
638 throw e;
639
640 if (log.isDebugEnabled())
641 log.debug(e, "Could not create JMS Consumer (retrying %d time)", retryCount);
642 else
643 log.info("Could not create JMS Consumer (retrying %d time)", retryCount);
644
645 try {
646 Thread.sleep(failoverRetryInterval);
647 }
648 catch (Exception f) {
649 throw new ServiceException("Could not sleep when retrying to create JMS Consumer", f.getMessage(), e);
650 }
651 }
652 }
653 while (retryCount-- > 0);
654
655 jmsConsumer.setMessageListener(this);
656 log.debug("Created JMS Consumer for channel %s", channel.getId());
657 }
658 catch (Exception e) {
659 close();
660 throw e;
661 }
662 }
663
664 public void setSelector(String selector) throws Exception {
665 if (jmsConsumer != null) {
666 jmsConsumer.close();
667 jmsConsumer = null;
668 }
669
670 connect(selector);
671 log.debug("Changed selector to %s for JMS Consumer of channel %s", selector, channel.getId());
672 }
673
674 public void reset() {
675 jmsConsumer = null;
676 jmsConsumerSession = null;
677
678 final TimerTask reconnectTask = new TimerTask() {
679 @Override
680 public void run() {
681 try {
682 connect(selector);
683 reconnectTimer.cancel();
684 reconnectTimer = null;
685 }
686 catch (Exception e) {
687 // Wait for next task run
688 }
689 }
690 };
691 if (reconnectTimer != null)
692 reconnectTimer.cancel();
693
694 reconnectTimer = new Timer();
695 reconnectTimer.schedule(reconnectTask, failoverRetryInterval, reconnectRetryInterval);
696 }
697
698 public void close() throws JMSException {
699 if (reconnectTimer != null)
700 reconnectTimer.cancel();
701
702 try {
703 if (jmsConsumer != null) {
704 jmsConsumer.close();
705 jmsConsumer = null;
706 }
707 }
708 finally {
709 if (jmsConsumerSession != null) {
710 jmsConsumerSession.close();
711 jmsConsumerSession = null;
712 }
713 }
714 }
715
716 public void onMessage(javax.jms.Message message) {
717 if (!(message instanceof ObjectMessage) && !(message instanceof TextMessage)) {
718 log.error("JMS Adapter message type not allowed: %s", message.getClass().getName());
719
720 try {
721 if (acknowledgeMode == Session.CLIENT_ACKNOWLEDGE)
722 message.acknowledge();
723
724 if (transactedSessions)
725 jmsConsumerSession.commit();
726 }
727 catch (JMSException e) {
728 log.error(e, "Could not ack/commit JMS onMessage");
729 }
730 }
731
732 log.debug("Delivering JMS message to channel %s subscription %s", channel.getId(), subscriptionId);
733
734 AsyncMessage dmsg = new AsyncMessage();
735 try {
736 Serializable msg = null;
737
738 if (textMessages) {
739 TextMessage jmsMessage = (TextMessage)message;
740 msg = jmsMessage.getText();
741 }
742 else {
743 ObjectMessage jmsMessage = (ObjectMessage)message;
744 if (useJBossTCCLDeserializationWorkaround) {
745 // On JBoss 6, try to deserialize with application class loader if the previous attempt fails
746 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
747 try {
748 Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
749 msg = jmsMessage.getObject();
750 }
751 finally {
752 Thread.currentThread().setContextClassLoader(contextClassLoader);
753 }
754 }
755 try {
756 msg = jmsMessage.getObject();
757 }
758 catch (JMSException e) {
759 // On JBoss 6, try to deserialize with application class loader if the previous attempt fails
760 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
761 try {
762 Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
763 msg = jmsMessage.getObject();
764 useJBossTCCLDeserializationWorkaround = true;
765 }
766 finally {
767 Thread.currentThread().setContextClassLoader(contextClassLoader);
768 }
769 }
770 }
771
772 dmsg.setDestination(getDestination().getId());
773
774 if (Boolean.TRUE.equals(message.getBooleanProperty(Gravity.BYTEARRAY_BODY_HEADER))) {
775 getGravity().initThread(null, channel.getClientType());
776 try {
777 ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
778 AMF3Serializer ser = new AMF3Serializer(baos);
779 ser.writeObject(msg);
780 ser.close();
781 baos.close();
782 dmsg.setBody(baos.toByteArray());
783 }
784 finally {
785 getGravity().releaseThread();
786 }
787 }
788 else
789 dmsg.setBody(msg);
790
791 dmsg.setMessageId(denormalizeJMSMessageID(message.getJMSMessageID()));
792 dmsg.setCorrelationId(denormalizeJMSMessageID(message.getJMSCorrelationID()));
793 dmsg.setTimestamp(message.getJMSTimestamp());
794 dmsg.setTimeToLive(message.getJMSExpiration());
795
796 Enumeration<?> ename = message.getPropertyNames();
797 while (ename.hasMoreElements()) {
798 String pname = (String)ename.nextElement();
799 dmsg.setHeader(pname, message.getObjectProperty(pname));
800 }
801
802 dmsg.setHeader("JMSType", message.getJMSType());
803 dmsg.setHeader("JMSPriority", Integer.valueOf(message.getJMSPriority()));
804 dmsg.setHeader("JMSRedelivered", Boolean.valueOf(message.getJMSRedelivered()));
805 dmsg.setHeader("JMSDeliveryMode", Integer.valueOf(message.getJMSDeliveryMode()));
806 dmsg.setHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER, subscriptionId);
807
808 channel.receive(dmsg);
809 }
810 catch (IOException e) {
811 if (transactedSessions) {
812 try {
813 jmsConsumerSession.rollback();
814 }
815 catch (JMSException f) {
816 log.error("Could not rollback JMS session, messageId: %s", dmsg.getMessageId());
817 }
818 }
819
820 throw new RuntimeException("IO Error", e);
821 }
822 catch (JMSException e) {
823 if (transactedSessions) {
824 try {
825 jmsConsumerSession.rollback();
826 }
827 catch (JMSException f) {
828 log.error("Could not rollback JMS session, messageId: %s", dmsg.getMessageId());
829 }
830 }
831
832 throw new RuntimeException("JMS Error", e);
833 }
834 catch (MessageReceivingException e) {
835 if (transactedSessions) {
836 try {
837 jmsConsumerSession.rollback();
838 }
839 catch (JMSException f) {
840 log.error("Could not rollback JMS session, messageId: %s", dmsg.getMessageId());
841 }
842 }
843
844 throw new RuntimeException("Channel delivery Error", e);
845 }
846
847 try {
848 if (acknowledgeMode == Session.CLIENT_ACKNOWLEDGE)
849 message.acknowledge();
850
851 if (transactedSessions && !useGlassFishNoCommitWorkaround)
852 jmsConsumerSession.commit();
853 }
854 catch (JMSException e) {
855 if (e.getMessage() != null && e.getMessage().startsWith("MQJMSRA_DS4001"))
856 useGlassFishNoCommitWorkaround = true;
857 else
858 log.error(e, "Could not ack/commit JMS onMessage, messageId: %s", dmsg.getMessageId());
859
860 // Message already delivered to client, should rollback or not ?
861 }
862 }
863
864 private String denormalizeJMSMessageID(String messageId) {
865 if (messageId != null && messageId.startsWith("ID:"))
866 messageId = messageId.substring(3);
867 return messageId;
868 }
869 }
870 }
871 }