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