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            try {
232                JMSClient jmsClient = connectJMSClient(fromClient, message.getDestination());
233                jmsClient.send(message);
234    
235                AsyncMessage reply = new AcknowledgeMessage(message);
236                reply.setMessageId(message.getMessageId());
237    
238                return reply;
239            }
240            catch (Exception e) {
241                    log.error(e, "Error sending message");
242                ErrorMessage error = new ErrorMessage(message, null);
243                error.setFaultString("JMS Adapter error " + e.getMessage());
244    
245                return error;
246            }
247        }
248    
249        @Override
250        public Object manage(Channel fromChannel, CommandMessage message) {
251            if (message.getOperation() == CommandMessage.SUBSCRIBE_OPERATION) {
252                try {
253                    JMSClient jmsClient = connectJMSClient(fromChannel, message.getDestination());
254                    jmsClient.subscribe(message);
255    
256                    AsyncMessage reply = new AcknowledgeMessage(message);
257                    return reply;
258                }
259                catch (Exception e) {
260                    throw new RuntimeException("JMSAdapter subscribe error on topic: " + message, e);
261                }
262            }
263            else if (message.getOperation() == CommandMessage.UNSUBSCRIBE_OPERATION) {
264                try {
265                    JMSClient jmsClient = connectJMSClient(fromChannel, message.getDestination());
266                    jmsClient.unsubscribe(message);
267                    closeJMSClientIfNecessary(fromChannel, message.getDestination());
268    
269                    AsyncMessage reply = new AcknowledgeMessage(message);
270                    return reply;
271                }
272                catch (Exception e) {
273                    throw new RuntimeException("JMSAdapter unsubscribe error on topic: " + message, e);
274                }
275            }
276    
277            return null;
278        }
279    
280    
281        @TransientReference
282        private class JMSClientImpl implements JMSClient {
283    
284            private Channel channel = null;
285            private String topic = null;
286            private javax.jms.Connection jmsConnection = null;
287            private javax.jms.Session jmsProducerSession = null;
288            private javax.jms.MessageProducer jmsProducer = null;
289            private Map<String, JMSConsumer> consumers = new HashMap<String, JMSConsumer>();
290            private boolean useGlassFishNoCommitWorkaround = false;
291    
292    
293            public JMSClientImpl(Channel channel) {
294                this.channel = channel;            
295            }
296    
297            public boolean hasActiveConsumer() {
298                return consumers != null && !consumers.isEmpty();
299            }
300    
301    
302            public void connect() throws ServiceException {
303                try {
304                    jmsConnection = jmsConnectionFactory.createConnection();
305                    jmsConnection.start();
306                }
307                catch (JMSException e) {
308                    throw new ServiceException("JMS Initialize error", e);
309                }
310            }
311    
312            public void close() throws ServiceException {
313                try {
314                    if (jmsProducer != null)
315                        jmsProducer.close();
316                }
317                catch (JMSException e) {
318                    log.error(e, "Could not close JMS Producer for channel " + channel.getId());
319                }
320                finally {
321                    try {
322                            if (jmsProducerSession != null)
323                                jmsProducerSession.close();
324                        }
325                        catch (JMSException e) {
326                            log.error(e, "Could not close JMS Producer Session for channel " + channel.getId());
327                        }
328                }
329                for (JMSConsumer consumer : consumers.values()) {
330                    try {
331                            consumer.close();
332                    }
333                    catch (JMSException e) {
334                            log.error(e, "Could not close JMS Consumer " + consumer.subscriptionId + " for channel " + channel.getId());
335                    }
336                }
337                try {
338                    jmsConnection.stop();
339                }
340                catch (JMSException e) {
341                    log.debug(e, "Could not stop JMS Connection for channel " + channel.getId());
342                }
343                finally {
344                            try {
345                                    jmsConnection.close();
346                            }
347                        catch (JMSException e) {
348                            throw new ServiceException("JMS Stop error", e);
349                        }
350                            finally {
351                                    consumers.clear();
352                            }
353                }
354            }
355            
356    
357            public void send(AsyncMessage message) throws Exception {
358                Object msg = null;
359                if (Boolean.TRUE.equals(message.getHeader(Gravity.BYTEARRAY_BODY_HEADER))) {
360                    byte[] byteArray = (byte[])message.getBody();
361                    ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
362                    AMF3Deserializer deser = new AMF3Deserializer(bais);
363                    msg = deser.readObject();
364                }
365                else
366                    msg = message.getBody();
367                
368                internalSend(message.getHeaders(), msg, message.getMessageId(), message.getCorrelationId(), message.getTimestamp(), message.getTimeToLive());
369            }
370    
371            public void send(Map<String, ?> params, Object msg, long timeToLive) throws Exception {
372                    internalSend(params, msg, null, null, new Date().getTime(), timeToLive);
373            }
374            
375            public void internalSend(Map<String, ?> headers, Object msg, String messageId, String correlationId, long timestamp, long timeToLive) throws Exception {
376                String topic = (String)headers.get(AsyncMessage.SUBTOPIC_HEADER);
377                    
378                if (jmsProducerSession == null) {
379                    jmsProducerSession = jmsConnection.createSession(transactedSessions, acknowledgeMode);
380                    log.debug("Created JMS Producer Session for channel %s (transacted: %s, ack: %s)", channel.getId(), transactedSessions, acknowledgeMode);
381                }
382                
383                if (jmsProducer == null) {
384                    try {
385                            // When failing over, JMS can be in a temporary illegal state. Give it some time to recover. 
386                            int retryCount = failoverRetryCount;
387                            do {
388                                    try {
389                                            jmsProducer = jmsProducerSession.createProducer(getProducerDestination(topic));
390                                            break;
391                                    }
392                                    catch (Exception e) {
393                                            if (retryCount <= 0)
394                                                    throw e;
395                                            
396                                            if (log.isDebugEnabled())
397                                                    log.debug(e, "Could not create JMS Producer (retrying %d time)", retryCount);
398                                            else
399                                                    log.info("Could not create JMS Producer (retrying %d time)", retryCount);
400                                            
401                                            try {
402                                                    Thread.sleep(failoverRetryInterval);
403                                            }
404                                            catch (Exception f) {
405                                                    throw new ServiceException("Could not sleep when retrying to create JMS Producer", f.getMessage(), e);
406                                            }
407                                    }
408                            }
409                            while (retryCount-- > 0);
410                            
411                            jmsProducer.setPriority(messagePriority);
412                            jmsProducer.setDeliveryMode(deliveryMode);
413                            log.debug("Created JMS Producer for channel %s", channel.getId());
414                    }
415                    catch (JMSException e) {
416                            jmsProducerSession.close();
417                            jmsProducerSession = null;
418                            throw e;
419                    }
420                }
421                
422                javax.jms.Message jmsMessage = null;
423                if (textMessages)
424                    jmsMessage = jmsProducerSession.createTextMessage(msg.toString());
425                else
426                    jmsMessage = jmsProducerSession.createObjectMessage((Serializable)msg);
427    
428                jmsMessage.setJMSMessageID(normalizeJMSMessageID(messageId));
429                jmsMessage.setJMSCorrelationID(normalizeJMSMessageID(correlationId));
430                jmsMessage.setJMSTimestamp(timestamp);
431                jmsMessage.setJMSExpiration(timeToLive);
432                
433                for (Map.Entry<String, ?> me : headers.entrySet()) {
434                    if ("JMSType".equals(me.getKey())) {
435                        if (me.getValue() instanceof String)
436                            jmsMessage.setJMSType((String)me.getValue());
437                    }
438                    else if ("JMSPriority".equals(me.getKey())) {
439                        if (me.getValue() instanceof Integer)
440                            jmsMessage.setJMSPriority(((Integer)me.getValue()).intValue());
441                    }
442                    else if (me.getValue() instanceof String)
443                        jmsMessage.setStringProperty(me.getKey(), (String)me.getValue());
444                    else if (me.getValue() instanceof Boolean)
445                        jmsMessage.setBooleanProperty(me.getKey(), ((Boolean)me.getValue()).booleanValue());
446                    else if (me.getValue() instanceof Integer)
447                        jmsMessage.setIntProperty(me.getKey(), ((Integer)me.getValue()).intValue());
448                    else if (me.getValue() instanceof Long)
449                        jmsMessage.setLongProperty(me.getKey(), ((Long)me.getValue()).longValue());
450                    else if (me.getValue() instanceof Double)
451                        jmsMessage.setDoubleProperty(me.getKey(), ((Double)me.getValue()).doubleValue());
452                    else
453                        jmsMessage.setObjectProperty(me.getKey(), me.getValue());
454                }
455    
456                jmsProducer.send(jmsMessage);
457                
458                if (transactedSessions && !useGlassFishNoCommitWorkaround) {
459                    // If we are in a container-managed transaction (data dispatch from an EJB interceptor for ex.), we should not commit the session
460                    // but the behaviour is different between JBoss and GlassFish
461                    try {
462                            jmsProducerSession.commit();
463                    }
464                    catch (JMSException e) {
465                            if (e.getMessage() != null && e.getMessage().startsWith("MQJMSRA_DS4001"))
466                            useGlassFishNoCommitWorkaround = true;
467                        else
468                                    log.error(e, "Could not commit JMS Session for channel %s", channel.getId());
469                    }
470                }
471            }
472    
473                    private String normalizeJMSMessageID(String messageId) {
474                if (messageId != null && !messageId.startsWith("ID:"))
475                    messageId = "ID:" + messageId;
476                            return messageId;
477                    }
478    
479            public void subscribe(CommandMessage message) throws Exception {
480                String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
481                String selector = (String)message.getHeader(CommandMessage.SELECTOR_HEADER);
482                this.topic = (String)message.getHeader(AsyncMessage.SUBTOPIC_HEADER);
483    
484                internalSubscribe(subscriptionId, selector, message.getDestination(), this.topic);
485            }
486            
487            public void subscribe(String selector, String destination, String topic) throws Exception {
488                    if (GraniteContext.getCurrentInstance() instanceof HttpGraniteContext) {
489                            String subscriptionId = GraniteDistributedDataFactory.getInstance().getDestinationSubscriptionId(destination);
490                            if (subscriptionId != null)
491                                    internalSubscribe(subscriptionId, selector, destination, topic);
492                    }
493            }
494            
495            private void internalSubscribe(String subscriptionId, String selector, String destination, String topic) throws Exception {
496                synchronized (consumers) {
497                    JMSConsumer consumer = consumers.get(subscriptionId);
498                    if (consumer == null) {
499                        consumer = new JMSConsumer(subscriptionId, selector, noLocal);
500                        consumers.put(subscriptionId, consumer);
501                    }
502                    else
503                        consumer.setSelector(selector);
504                    channel.addSubscription(destination, topic, subscriptionId, false);
505                }
506            }
507    
508            public void unsubscribe(CommandMessage message) throws Exception {
509                String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
510    
511                synchronized (consumers) {
512                    JMSConsumer consumer = consumers.get(subscriptionId);
513                    try {
514                            if (consumer != null)
515                                consumer.close();
516                    }
517                    finally {
518                            consumers.remove(subscriptionId);
519                            channel.removeSubscription(subscriptionId);
520                    }
521                }
522            }
523    
524    
525            private class JMSConsumer implements MessageListener {
526    
527                private String subscriptionId = null;
528                private javax.jms.Session jmsConsumerSession = null;
529                private javax.jms.MessageConsumer jmsConsumer = null;
530                private boolean noLocal = false;
531                private boolean useJBossTCCLDeserializationWorkaround = false;
532                private boolean useGlassFishNoCommitWorkaround = false;
533    
534                public JMSConsumer(String subscriptionId, String selector, boolean noLocal) throws Exception {
535                    this.subscriptionId = subscriptionId;
536                    this.noLocal = noLocal;
537                    
538                    jmsConsumerSession = jmsConnection.createSession(transactedSessions, acknowledgeMode);
539                    log.debug("Created JMS Consumer Session for channel %s (transacted: %s, ack: %s)", channel.getId(), transactedSessions, acknowledgeMode);
540                    
541                    try {                   
542                            // When failing over, JMS can be in a temporary illegal state. Give it some time to recover. 
543                            int retryCount = failoverRetryCount;
544                            do {
545                                    try {
546                                            jmsConsumer = jmsConsumerSession.createConsumer(getConsumerDestination(topic), selector, noLocal);
547                                            break;
548                                    }
549                                    catch (Exception e) {
550                                            if (retryCount <= 0)
551                                                    throw e;
552                                            
553                                            if (log.isDebugEnabled())
554                                                    log.debug(e, "Could not create JMS Consumer (retrying %d time)", retryCount);
555                                            else
556                                                    log.info("Could not create JMS Consumer (retrying %d time)", retryCount);
557                                            
558                                            try {
559                                                    Thread.sleep(failoverRetryInterval);
560                                            }
561                                            catch (Exception f) {
562                                                    throw new ServiceException("Could not sleep when retrying to create JMS Consumer", f.getMessage(), e);
563                                            }
564                                    }
565                            }
566                            while (retryCount-- > 0);
567                            
568                            jmsConsumer.setMessageListener(this);
569                            log.debug("Created JMS Consumer for channel %s", channel.getId());
570                    }
571                    catch (Exception e) {
572                            close();
573                            throw e;
574                    }
575                }
576    
577                public void setSelector(String selector) throws JMSException {
578                    if (jmsConsumer != null) {
579                        jmsConsumer.close();
580                        jmsConsumer = null;
581                    }
582                    jmsConsumer = jmsConsumerSession.createConsumer(getConsumerDestination(topic), selector, noLocal);
583                    jmsConsumer.setMessageListener(this);
584                    log.debug("Changed selector to %s for JMS Consumer of channel %s", selector, channel.getId());
585                }
586    
587                public void close() throws JMSException {
588                    try {
589                            if (jmsConsumer != null) {
590                                jmsConsumer.close();
591                                jmsConsumer = null;
592                            }
593                    }
594                    finally {
595                            if (jmsConsumerSession != null) {
596                                jmsConsumerSession.close();
597                                jmsConsumerSession = null;
598                            }
599                    }
600                }
601    
602                public void onMessage(javax.jms.Message message) {
603                    if (!(message instanceof ObjectMessage) && !(message instanceof TextMessage)) {
604                        log.error("JMS Adapter message type not allowed: %s", message.getClass().getName());
605    
606                        try {
607                            if (acknowledgeMode == Session.CLIENT_ACKNOWLEDGE)
608                                message.acknowledge();
609    
610                            if (transactedSessions)
611                                jmsConsumerSession.commit();
612                        }
613                        catch (JMSException e) {
614                            log.error(e, "Could not ack/commit JMS onMessage");
615                        }
616                    }
617    
618                    log.debug("Delivering JMS message to channel %s subscription %s", channel.getId(), subscriptionId);
619                    
620                    AsyncMessage dmsg = new AsyncMessage();
621                    try {
622                        Serializable msg = null;
623    
624                        if (textMessages) {
625                            TextMessage jmsMessage = (TextMessage)message;
626                            msg = jmsMessage.getText();
627                        }
628                        else {
629                            ObjectMessage jmsMessage = (ObjectMessage)message;
630                            if (useJBossTCCLDeserializationWorkaround) {
631                                    // On JBoss 6, try to deserialize with application class loader if the previous attempt fails
632                                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
633                                try {
634                                        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
635                                        msg = jmsMessage.getObject();
636                                }
637                                finally {
638                                    Thread.currentThread().setContextClassLoader(contextClassLoader);
639                                }
640                            }
641                            try {
642                                    msg = jmsMessage.getObject();
643                            }
644                            catch (JMSException e) {
645                                    // On JBoss 6, try to deserialize with application class loader if the previous attempt fails
646                                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
647                                try {
648                                        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
649                                        msg = jmsMessage.getObject();
650                                        useJBossTCCLDeserializationWorkaround = true;
651                                }
652                                finally {
653                                    Thread.currentThread().setContextClassLoader(contextClassLoader);
654                                }
655                            }
656                        }
657    
658                        dmsg.setDestination(getDestination().getId());
659                        
660                        if (Boolean.TRUE.equals(message.getBooleanProperty(Gravity.BYTEARRAY_BODY_HEADER))) {
661                            getGravity().initThread();
662                            try {
663                                    ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
664                                    AMF3Serializer ser = new AMF3Serializer(baos, false);
665                                    ser.writeObject(msg);
666                                    ser.close();
667                                    baos.close();
668                                    dmsg.setBody(baos.toByteArray());
669                            }
670                            finally {
671                                    getGravity().releaseThread();
672                            }
673                        }
674                        else
675                            dmsg.setBody(msg);
676                        
677                        dmsg.setMessageId(denormalizeJMSMessageID(message.getJMSMessageID()));
678                        dmsg.setCorrelationId(denormalizeJMSMessageID(message.getJMSCorrelationID()));
679                        dmsg.setTimestamp(message.getJMSTimestamp());
680                        dmsg.setTimeToLive(message.getJMSExpiration());
681    
682                        Enumeration<?> ename = message.getPropertyNames();
683                        while (ename.hasMoreElements()) {
684                            String pname = (String)ename.nextElement();
685                            dmsg.setHeader(pname, message.getObjectProperty(pname));
686                        }
687                            
688                        dmsg.setHeader("JMSType", message.getJMSType());
689                        dmsg.setHeader("JMSPriority", Integer.valueOf(message.getJMSPriority()));
690                        dmsg.setHeader("JMSRedelivered", Boolean.valueOf(message.getJMSRedelivered()));
691                        dmsg.setHeader("JMSDeliveryMode", Integer.valueOf(message.getJMSDeliveryMode()));
692                        dmsg.setHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER, subscriptionId);
693                        
694                        channel.receive(dmsg);
695                    }
696                    catch (IOException e) {
697                        if (transactedSessions) {
698                            try {
699                                jmsConsumerSession.rollback();
700                            }
701                            catch (JMSException f) {
702                                log.error("Could not rollback JMS session, messageId: %s", dmsg.getMessageId());
703                            }
704                        }
705    
706                        throw new RuntimeException("IO Error", e);
707                    }
708                    catch (JMSException e) {
709                        if (transactedSessions) {
710                            try {
711                                jmsConsumerSession.rollback();
712                            }
713                            catch (JMSException f) {
714                                log.error("Could not rollback JMS session, messageId: %s", dmsg.getMessageId());
715                            }
716                        }
717    
718                        throw new RuntimeException("JMS Error", e);
719                    }
720                    catch (MessageReceivingException e) {
721                        if (transactedSessions) {
722                            try {
723                                jmsConsumerSession.rollback();
724                            }
725                            catch (JMSException f) {
726                                log.error("Could not rollback JMS session, messageId: %s", dmsg.getMessageId());
727                            }
728                        }
729    
730                        throw new RuntimeException("Channel delivery Error", e);
731                    }
732    
733                    try {
734                        if (acknowledgeMode == Session.CLIENT_ACKNOWLEDGE)
735                            message.acknowledge();
736    
737                        if (transactedSessions && !useGlassFishNoCommitWorkaround)
738                            jmsConsumerSession.commit();
739                    }
740                    catch (JMSException e) {
741                        if (e.getMessage() != null && e.getMessage().startsWith("MQJMSRA_DS4001"))
742                            useGlassFishNoCommitWorkaround = true;
743                        else
744                            log.error(e, "Could not ack/commit JMS onMessage, messageId: %s", dmsg.getMessageId());
745    
746                        // Message already delivered to client, should rollback or not ?
747                    }
748                }
749    
750                    private String denormalizeJMSMessageID(String messageId) {
751                    if (messageId != null && messageId.startsWith("ID:"))
752                            messageId = messageId.substring(3);
753                            return messageId;
754                    }
755            }
756        }
757    }