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 */ 022package org.granite.gravity.adapters; 023 024import java.io.ByteArrayInputStream; 025import java.io.ByteArrayOutputStream; 026import java.io.IOException; 027import java.io.Serializable; 028import java.util.Date; 029import java.util.Enumeration; 030import java.util.HashMap; 031import java.util.Map; 032import java.util.Properties; 033import java.util.Timer; 034import java.util.TimerTask; 035 036import javax.jms.ConnectionFactory; 037import javax.jms.Destination; 038import javax.jms.ExceptionListener; 039import javax.jms.JMSException; 040import javax.jms.MessageListener; 041import javax.jms.ObjectMessage; 042import javax.jms.Session; 043import javax.jms.TextMessage; 044import javax.naming.Context; 045import javax.naming.InitialContext; 046import javax.naming.NamingException; 047 048import org.granite.clustering.DistributedDataFactory; 049import org.granite.clustering.TransientReference; 050import org.granite.context.GraniteContext; 051import org.granite.gravity.Channel; 052import org.granite.gravity.Gravity; 053import org.granite.gravity.MessageReceivingException; 054import org.granite.logging.Logger; 055import org.granite.messaging.amf.io.AMF3Deserializer; 056import org.granite.messaging.amf.io.AMF3Serializer; 057import org.granite.messaging.service.ServiceException; 058import org.granite.messaging.webapp.ServletGraniteContext; 059import org.granite.util.XMap; 060 061import flex.messaging.messages.AcknowledgeMessage; 062import flex.messaging.messages.AsyncMessage; 063import flex.messaging.messages.CommandMessage; 064import flex.messaging.messages.ErrorMessage; 065 066/** 067 * @author William DRAI 068 */ 069public 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 (jmsConsumer != 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 if (jmsConsumerSession == null) { 619 jmsConsumerSession = jmsConnection.createSession(transactedSessions, acknowledgeMode); 620 if (reconnected) 621 jmsConsumerSession.recover(); 622 log.debug("Created JMS Consumer Session for channel %s (transacted: %s, ack: %s)", channel.getId(), transactedSessions, acknowledgeMode); 623 } 624 625 if (reconnectTimer != null) 626 reconnectTimer.cancel(); 627 628 try { 629 // When failing over, JMS can be in a temporary illegal state. Give it some time to recover. 630 int retryCount = failoverRetryCount; 631 do { 632 try { 633 jmsConsumer = jmsConsumerSession.createConsumer(getConsumerDestination(topic), selector, noLocal); 634 if (retryCount < failoverRetryCount) // We come from a failover, try to recover session 635 reconnected = true; 636 break; 637 } 638 catch (Exception e) { 639 if (retryCount <= 0) 640 throw e; 641 642 if (log.isDebugEnabled()) 643 log.debug(e, "Could not create JMS Consumer (retrying %d time)", retryCount); 644 else 645 log.info("Could not create JMS Consumer (retrying %d time)", retryCount); 646 647 try { 648 Thread.sleep(failoverRetryInterval); 649 } 650 catch (Exception f) { 651 throw new ServiceException("Could not sleep when retrying to create JMS Consumer", f.getMessage(), e); 652 } 653 } 654 } 655 while (retryCount-- > 0); 656 657 jmsConsumer.setMessageListener(this); 658 log.debug("Created JMS Consumer for channel %s", channel.getId()); 659 } 660 catch (Exception e) { 661 close(); 662 throw e; 663 } 664 } 665 666 public void setSelector(String selector) throws Exception { 667 if (jmsConsumer != null) { 668 jmsConsumer.close(); 669 jmsConsumer = null; 670 } 671 672 connect(selector); 673 log.debug("Changed selector to %s for JMS Consumer of channel %s", selector, channel.getId()); 674 } 675 676 public void reset() { 677 jmsConsumer = null; 678 jmsConsumerSession = null; 679 680 final TimerTask reconnectTask = new TimerTask() { 681 @Override 682 public void run() { 683 try { 684 connect(selector); 685 reconnectTimer.cancel(); 686 reconnectTimer = null; 687 } 688 catch (Exception e) { 689 // Wait for next task run 690 } 691 } 692 }; 693 if (reconnectTimer != null) 694 reconnectTimer.cancel(); 695 696 reconnectTimer = new Timer(); 697 reconnectTimer.schedule(reconnectTask, failoverRetryInterval, reconnectRetryInterval); 698 } 699 700 public void close() throws JMSException { 701 if (reconnectTimer != null) 702 reconnectTimer.cancel(); 703 704 try { 705 if (jmsConsumer != null) { 706 jmsConsumer.close(); 707 jmsConsumer = null; 708 } 709 } 710 finally { 711 if (jmsConsumerSession != null) { 712 jmsConsumerSession.close(); 713 jmsConsumerSession = null; 714 } 715 } 716 } 717 718 public void onMessage(javax.jms.Message message) { 719 if (!(message instanceof ObjectMessage) && !(message instanceof TextMessage)) { 720 log.error("JMS Adapter message type not allowed: %s", message.getClass().getName()); 721 722 try { 723 if (acknowledgeMode == Session.CLIENT_ACKNOWLEDGE) 724 message.acknowledge(); 725 726 if (transactedSessions) 727 jmsConsumerSession.commit(); 728 } 729 catch (JMSException e) { 730 log.error(e, "Could not ack/commit JMS onMessage"); 731 } 732 } 733 734 log.debug("Delivering JMS message to channel %s subscription %s", channel.getId(), subscriptionId); 735 736 AsyncMessage dmsg = new AsyncMessage(); 737 try { 738 Serializable msg = null; 739 740 if (textMessages) { 741 TextMessage jmsMessage = (TextMessage)message; 742 msg = jmsMessage.getText(); 743 } 744 else { 745 ObjectMessage jmsMessage = (ObjectMessage)message; 746 if (useJBossTCCLDeserializationWorkaround) { 747 // On JBoss 6, try to deserialize with application class loader if the previous attempt fails 748 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 749 try { 750 Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); 751 msg = jmsMessage.getObject(); 752 } 753 finally { 754 Thread.currentThread().setContextClassLoader(contextClassLoader); 755 } 756 } 757 try { 758 msg = jmsMessage.getObject(); 759 } 760 catch (JMSException e) { 761 // On JBoss 6, try to deserialize with application class loader if the previous attempt fails 762 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); 763 try { 764 Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); 765 msg = jmsMessage.getObject(); 766 useJBossTCCLDeserializationWorkaround = true; 767 } 768 finally { 769 Thread.currentThread().setContextClassLoader(contextClassLoader); 770 } 771 } 772 } 773 774 dmsg.setDestination(getDestination().getId()); 775 776 if (Boolean.TRUE.equals(message.getBooleanProperty(Gravity.BYTEARRAY_BODY_HEADER))) { 777 getGravity().initThread(null, channel.getClientType()); 778 try { 779 ByteArrayOutputStream baos = new ByteArrayOutputStream(100); 780 AMF3Serializer ser = new AMF3Serializer(baos); 781 ser.writeObject(msg); 782 ser.close(); 783 baos.close(); 784 dmsg.setBody(baos.toByteArray()); 785 } 786 finally { 787 getGravity().releaseThread(); 788 } 789 } 790 else 791 dmsg.setBody(msg); 792 793 dmsg.setMessageId(denormalizeJMSMessageID(message.getJMSMessageID())); 794 dmsg.setCorrelationId(denormalizeJMSMessageID(message.getJMSCorrelationID())); 795 dmsg.setTimestamp(message.getJMSTimestamp()); 796 dmsg.setTimeToLive(message.getJMSExpiration()); 797 798 Enumeration<?> ename = message.getPropertyNames(); 799 while (ename.hasMoreElements()) { 800 String pname = (String)ename.nextElement(); 801 dmsg.setHeader(pname, message.getObjectProperty(pname)); 802 } 803 804 dmsg.setHeader("JMSType", message.getJMSType()); 805 dmsg.setHeader("JMSPriority", Integer.valueOf(message.getJMSPriority())); 806 dmsg.setHeader("JMSRedelivered", Boolean.valueOf(message.getJMSRedelivered())); 807 dmsg.setHeader("JMSDeliveryMode", Integer.valueOf(message.getJMSDeliveryMode())); 808 dmsg.setHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER, subscriptionId); 809 810 channel.receive(dmsg); 811 } 812 catch (IOException e) { 813 if (transactedSessions) { 814 try { 815 jmsConsumerSession.rollback(); 816 } 817 catch (JMSException f) { 818 log.error("Could not rollback JMS session, messageId: %s", dmsg.getMessageId()); 819 } 820 } 821 822 throw new RuntimeException("IO Error", e); 823 } 824 catch (JMSException e) { 825 if (transactedSessions) { 826 try { 827 jmsConsumerSession.rollback(); 828 } 829 catch (JMSException f) { 830 log.error("Could not rollback JMS session, messageId: %s", dmsg.getMessageId()); 831 } 832 } 833 834 throw new RuntimeException("JMS Error", e); 835 } 836 catch (MessageReceivingException e) { 837 if (transactedSessions) { 838 try { 839 jmsConsumerSession.rollback(); 840 } 841 catch (JMSException f) { 842 log.error("Could not rollback JMS session, messageId: %s", dmsg.getMessageId()); 843 } 844 } 845 846 throw new RuntimeException("Channel delivery Error", e); 847 } 848 849 try { 850 if (acknowledgeMode == Session.CLIENT_ACKNOWLEDGE) 851 message.acknowledge(); 852 853 if (transactedSessions && !useGlassFishNoCommitWorkaround) 854 jmsConsumerSession.commit(); 855 } 856 catch (JMSException e) { 857 if (e.getMessage() != null && e.getMessage().startsWith("MQJMSRA_DS4001")) 858 useGlassFishNoCommitWorkaround = true; 859 else 860 log.error(e, "Could not ack/commit JMS onMessage, messageId: %s", dmsg.getMessageId()); 861 862 // Message already delivered to client, should rollback or not ? 863 } 864 } 865 866 private String denormalizeJMSMessageID(String messageId) { 867 if (messageId != null && messageId.startsWith("ID:")) 868 messageId = messageId.substring(3); 869 return messageId; 870 } 871 } 872 } 873}