001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.component.jms;
018    
019    import javax.jms.Destination;
020    import javax.jms.JMSException;
021    import javax.jms.Message;
022    import javax.jms.MessageListener;
023    import javax.jms.Session;
024    
025    import org.apache.camel.Exchange;
026    import org.apache.camel.ExchangePattern;
027    import org.apache.camel.Processor;
028    import org.apache.camel.RollbackExchangeException;
029    import org.apache.camel.RuntimeCamelException;
030    import org.apache.camel.impl.DefaultExchange;
031    import org.apache.camel.impl.LoggingExceptionHandler;
032    import org.apache.camel.spi.ExceptionHandler;
033    import org.apache.camel.util.ObjectHelper;
034    import org.apache.commons.logging.Log;
035    import org.apache.commons.logging.LogFactory;
036    import org.springframework.jms.core.JmsOperations;
037    import org.springframework.jms.core.MessageCreator;
038    
039    import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException;
040    
041    /**
042     * A JMS {@link MessageListener} which can be used to delegate processing to a
043     * Camel endpoint.
044     *
045     * Note that instance of this object has to be thread safe (reentrant)
046     *
047     * @version $Revision: 20215 $
048     */
049    public class EndpointMessageListener implements MessageListener {
050        private static final transient Log LOG = LogFactory.getLog(EndpointMessageListener.class);
051        private ExceptionHandler exceptionHandler;
052        private JmsEndpoint endpoint;
053        private Processor processor;
054        private JmsBinding binding;
055        private boolean eagerLoadingOfProperties;
056        private Object replyToDestination;
057        private JmsOperations template;
058        private boolean disableReplyTo;
059    
060        public EndpointMessageListener(JmsEndpoint endpoint, Processor processor) {
061            this.endpoint = endpoint;
062            this.processor = processor;
063            endpoint.getConfiguration().configure(this);
064        }
065    
066        public void onMessage(final Message message) {
067            LOG.trace("onMessage START");
068    
069            if (LOG.isDebugEnabled()) {
070                LOG.debug(endpoint + " consumer receiving JMS message: " + message);
071            }
072    
073            RuntimeCamelException rce = null;
074            try {
075                Object replyDestination = getReplyToDestination(message);
076                final Exchange exchange = createExchange(message, replyDestination);
077                if (eagerLoadingOfProperties) {
078                    exchange.getIn().getHeaders();
079                }
080    
081                // process the exchange
082                if (LOG.isTraceEnabled()) {
083                    LOG.trace("onMessage.process START");
084                }
085                processor.process(exchange);
086                if (LOG.isTraceEnabled()) {
087                    LOG.trace("onMessage.process END");
088                }
089    
090                // get the correct jms message to send as reply
091                JmsMessage body = null;
092                Exception cause = null;
093                boolean sendReply = false;
094                if (exchange.isFailed() || exchange.isRollbackOnly()) {
095                    if (exchange.getException() != null) {
096                        // an exception occurred while processing
097                        if (endpoint.isTransferException()) {
098                            // send the exception as reply
099                            body = null;
100                            cause = exchange.getException();
101                            sendReply = true;
102                        } else {
103                            // only throw exception if endpoint is not configured to transfer exceptions back to caller
104                            // do not send a reply but wrap and rethrow the exception
105                            rce = wrapRuntimeCamelException(exchange.getException());
106                        }
107                    } else if (exchange.isRollbackOnly()) {
108                        // rollback only so wrap an exception so we can rethrow the exception to cause rollback
109                        rce = wrapRuntimeCamelException(new RollbackExchangeException(exchange));
110                    } else if (exchange.getOut().getBody() != null) {
111                        // a fault occurred while processing
112                        body = (JmsMessage) exchange.getOut();
113                        sendReply = true;
114                    }
115                } else if (exchange.hasOut()) {
116                    // process OK so get the reply
117                    body = (JmsMessage) exchange.getOut();
118                    sendReply = true;
119                }
120    
121                // send the reply if we got a response and the exchange is out capable
122                if (rce == null && sendReply && !disableReplyTo && exchange.getPattern().isOutCapable()) {
123                    LOG.trace("onMessage.sendReply START");
124                    if (replyDestination instanceof Destination) {
125                        sendReply((Destination)replyDestination, message, exchange, body, cause);
126                    } else {
127                        sendReply((String)replyDestination, message, exchange, body, cause);
128                    }
129                    LOG.trace("onMessage.sendReply END");
130                }
131    
132            } catch (Exception e) {
133                rce = wrapRuntimeCamelException(e);
134            }
135    
136            if (rce != null) {
137                handleException(rce);
138                if (LOG.isTraceEnabled()) {
139                    LOG.trace("onMessage END throwing exception: " + rce.getMessage());
140                }
141                throw rce;
142            }
143    
144            LOG.trace("onMessage END");
145        }
146    
147        public Exchange createExchange(Message message, Object replyDestination) {
148            Exchange exchange = new DefaultExchange(endpoint, endpoint.getExchangePattern());
149            JmsBinding binding = getBinding();
150            exchange.setProperty(Exchange.BINDING, binding);
151            exchange.setIn(new JmsMessage(message, binding));
152    
153            // lets set to an InOut if we have some kind of reply-to destination
154            if (replyDestination != null && !disableReplyTo) {
155                // only change pattern if not already out capable
156                if (!exchange.getPattern().isOutCapable()) {
157                    exchange.setPattern(ExchangePattern.InOut);
158                }
159            }
160            return exchange;
161        }
162    
163        // Properties
164        // -------------------------------------------------------------------------
165        public JmsBinding getBinding() {
166            if (binding == null) {
167                binding = new JmsBinding(endpoint);
168            }
169            return binding;
170        }
171    
172        /**
173         * Sets the binding used to convert from a Camel message to and from a JMS
174         * message
175         *
176         * @param binding the binding to use
177         */
178        public void setBinding(JmsBinding binding) {
179            this.binding = binding;
180        }
181    
182        public ExceptionHandler getExceptionHandler() {
183            if (exceptionHandler == null) {
184                exceptionHandler = new LoggingExceptionHandler(getClass());
185            }
186            return exceptionHandler;
187        }
188    
189        public void setExceptionHandler(ExceptionHandler exceptionHandler) {
190            this.exceptionHandler = exceptionHandler;
191        }
192    
193        public boolean isEagerLoadingOfProperties() {
194            return eagerLoadingOfProperties;
195        }
196    
197        public void setEagerLoadingOfProperties(boolean eagerLoadingOfProperties) {
198            this.eagerLoadingOfProperties = eagerLoadingOfProperties;
199        }
200    
201        public synchronized JmsOperations getTemplate() {
202            if (template == null) {
203                template = endpoint.createInOnlyTemplate();
204            }
205            return template;
206        }
207    
208        public void setTemplate(JmsOperations template) {
209            this.template = template;
210        }
211    
212        public boolean isDisableReplyTo() {
213            return disableReplyTo;
214        }
215    
216        /**
217         * Allows the reply-to behaviour to be disabled
218         */
219        public void setDisableReplyTo(boolean disableReplyTo) {
220            this.disableReplyTo = disableReplyTo;
221        }
222    
223        public Object getReplyToDestination() {
224            return replyToDestination;
225        }
226    
227        /**
228         * Provides an explicit reply to destination which overrides
229         * any incoming value of {@link Message#getJMSReplyTo()}
230         *
231         * @param replyToDestination the destination that should be used to send replies to
232         * as either a String or {@link javax.jms.Destination} type.
233         */
234        public void setReplyToDestination(Object replyToDestination) {
235            this.replyToDestination = replyToDestination;
236        }
237    
238        // Implementation methods
239        //-------------------------------------------------------------------------
240    
241        /**
242         * Strategy to determine which correlation id to use among <tt>JMSMessageID</tt> and <tt>JMSCorrelationID</tt>.
243         *
244         * @param message the JMS message
245         * @return the correlation id to use
246         * @throws JMSException can be thrown
247         */
248        protected String determineCorrelationId(final Message message) throws JMSException {
249            final String messageId = message.getJMSMessageID();
250            final String correlationId = message.getJMSCorrelationID();
251    
252            if (endpoint.getConfiguration().isUseMessageIDAsCorrelationID()) {
253                return messageId;
254            } else if (ObjectHelper.isEmpty(correlationId)) {
255                // correlation id is empty so fallback to message id
256                return messageId;
257            } else {
258                return correlationId;
259            }
260        }
261    
262        protected void sendReply(Destination replyDestination, final Message message, final Exchange exchange,
263                                 final JmsMessage out, final Exception cause) {
264            if (replyDestination == null) {
265                if (LOG.isDebugEnabled()) {
266                    LOG.debug("Cannot send reply message as there is no replyDestination for: " + out);
267                }
268                return;
269            }
270            getTemplate().send(replyDestination, new MessageCreator() {
271                public Message createMessage(Session session) throws JMSException {
272                    Message reply = endpoint.getBinding().makeJmsMessage(exchange, out, session, cause);
273                    final String correlationID = determineCorrelationId(message);
274                    reply.setJMSCorrelationID(correlationID);
275    
276                    if (LOG.isDebugEnabled()) {
277                        LOG.debug(endpoint + " sending reply JMS message [correlationId:" + correlationID + "]: " + reply);
278                    }
279                    return reply;
280                }
281            });
282        }
283    
284        protected void sendReply(String replyDestination, final Message message, final Exchange exchange,
285                                 final JmsMessage out, final Exception cause) {
286            if (replyDestination == null) {
287                if (LOG.isDebugEnabled()) {
288                    LOG.debug("Cannot send reply message as there is no replyDestination for: " + out);
289                }
290                return;
291            }
292            getTemplate().send(replyDestination, new MessageCreator() {
293                public Message createMessage(Session session) throws JMSException {
294                    Message reply = endpoint.getBinding().makeJmsMessage(exchange, out, session, cause);
295    
296                    if (endpoint.getConfiguration().isUseMessageIDAsCorrelationID()) {
297                        String messageID = exchange.getIn().getHeader("JMSMessageID", String.class);
298                        reply.setJMSCorrelationID(messageID);
299                    } else {
300                        String correlationID = message.getJMSCorrelationID();
301                        if (correlationID != null) {
302                            reply.setJMSCorrelationID(correlationID);
303                        }
304                    }
305    
306                    if (LOG.isDebugEnabled()) {
307                        LOG.debug(endpoint + " sending reply JMS message: " + reply);
308                    }
309                    return reply;
310                }
311            });
312        }
313    
314        protected Object getReplyToDestination(Message message) throws JMSException {
315            // lets send a response back if we can
316            Object destination = getReplyToDestination();
317            if (destination == null) {
318                try {
319                    destination = message.getJMSReplyTo();
320                } catch (JMSException e) {
321                    LOG.trace("Cannot read JMSReplyTo header. Will ignore this exception.", e);
322                }
323            }
324            return destination;
325        }
326    
327        /**
328         * Handles the given exception using the {@link #getExceptionHandler()}
329         *
330         * @param t the exception to handle
331         */
332        protected void handleException(Throwable t) {
333            getExceptionHandler().handleException(t);
334        }
335    
336    }