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 }