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 (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}