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 java.util.concurrent.FutureTask;
020 import java.util.concurrent.TimeUnit;
021 import java.util.concurrent.TimeoutException;
022 import java.util.concurrent.atomic.AtomicBoolean;
023
024 import javax.jms.Destination;
025 import javax.jms.JMSException;
026 import javax.jms.Message;
027 import javax.jms.Session;
028
029 import org.apache.camel.Exchange;
030 import org.apache.camel.ExchangeTimedOutException;
031 import org.apache.camel.FailedToCreateProducerException;
032 import org.apache.camel.RuntimeCamelException;
033 import org.apache.camel.RuntimeExchangeException;
034 import org.apache.camel.component.jms.JmsConfiguration.CamelJmsTemplate;
035 import org.apache.camel.component.jms.JmsConfiguration.CamelJmsTemplate102;
036 import org.apache.camel.component.jms.requestor.DeferredRequestReplyMap;
037 import org.apache.camel.component.jms.requestor.DeferredRequestReplyMap.DeferredMessageSentCallback;
038 import org.apache.camel.component.jms.requestor.PersistentReplyToRequestor;
039 import org.apache.camel.component.jms.requestor.Requestor;
040 import org.apache.camel.impl.DefaultProducer;
041 import org.apache.camel.util.UuidGenerator;
042 import org.apache.camel.util.ValueHolder;
043 import org.apache.commons.logging.Log;
044 import org.apache.commons.logging.LogFactory;
045 import org.springframework.jms.core.JmsOperations;
046 import org.springframework.jms.core.MessageCreator;
047
048 /**
049 * @version $Revision: 21400 $
050 */
051 public class JmsProducer extends DefaultProducer {
052 private static final transient Log LOG = LogFactory.getLog(JmsProducer.class);
053 private RequestorAffinity affinity;
054 private final JmsEndpoint endpoint;
055 private JmsOperations inOnlyTemplate;
056 private JmsOperations inOutTemplate;
057 private UuidGenerator uuidGenerator;
058 private DeferredRequestReplyMap deferredRequestReplyMap;
059 private Requestor requestor;
060 private AtomicBoolean started = new AtomicBoolean(false);
061
062 private enum RequestorAffinity {
063 PER_COMPONENT(0),
064 PER_ENDPOINT(1),
065 PER_PRODUCER(2);
066 private int value;
067 private RequestorAffinity(int value) {
068 this.value = value;
069 }
070 }
071
072 public JmsProducer(JmsEndpoint endpoint) {
073 super(endpoint);
074 this.endpoint = endpoint;
075 JmsConfiguration c = endpoint.getConfiguration();
076 affinity = RequestorAffinity.PER_PRODUCER;
077 if (c.getReplyTo() != null) {
078 if (c.getReplyToTempDestinationAffinity().equals(JmsConfiguration.REPLYTO_TEMP_DEST_AFFINITY_PER_ENDPOINT)) {
079 affinity = RequestorAffinity.PER_ENDPOINT;
080 } else if (c.getReplyToTempDestinationAffinity().equals(JmsConfiguration.REPLYTO_TEMP_DEST_AFFINITY_PER_COMPONENT)) {
081 affinity = RequestorAffinity.PER_COMPONENT;
082 }
083 }
084 }
085
086 public long getRequestTimeout() {
087 return endpoint.getConfiguration().getRequestTimeout();
088 }
089
090 protected void doStart() throws Exception {
091 super.doStart();
092 }
093
094 protected void testAndSetRequestor() throws RuntimeCamelException {
095 if (!started.get()) {
096 synchronized (this) {
097 if (started.get()) {
098 return;
099 }
100 try {
101 JmsConfiguration c = endpoint.getConfiguration();
102 if (c.getReplyTo() != null) {
103 requestor = new PersistentReplyToRequestor(endpoint.getConfiguration(), endpoint.getScheduledExecutorService());
104 requestor.start();
105 } else {
106 if (affinity == RequestorAffinity.PER_PRODUCER) {
107 requestor = new Requestor(endpoint.getConfiguration(), endpoint.getScheduledExecutorService());
108 requestor.start();
109 } else if (affinity == RequestorAffinity.PER_ENDPOINT) {
110 requestor = endpoint.getRequestor();
111 } else if (affinity == RequestorAffinity.PER_COMPONENT) {
112 requestor = ((JmsComponent)endpoint.getComponent()).getRequestor();
113 }
114 }
115 } catch (Exception e) {
116 throw new FailedToCreateProducerException(endpoint, e);
117 }
118 deferredRequestReplyMap = requestor.getDeferredRequestReplyMap(this);
119 started.set(true);
120 }
121 }
122 }
123
124 protected void testAndUnsetRequestor() throws Exception {
125 if (started.get()) {
126 synchronized (this) {
127 if (!started.get()) {
128 return;
129 }
130 requestor.removeDeferredRequestReplyMap(this);
131 if (affinity == RequestorAffinity.PER_PRODUCER) {
132 requestor.stop();
133 }
134 started.set(false);
135 }
136 }
137 }
138
139 protected void doStop() throws Exception {
140 testAndUnsetRequestor();
141 super.doStop();
142 }
143
144 public void process(final Exchange exchange) {
145 if (!endpoint.isDisableReplyTo() && exchange.getPattern().isOutCapable()) {
146 // in out requires a bit more work than in only
147 processInOut(exchange);
148 } else {
149 // in only
150 processInOnly(exchange);
151 }
152 }
153
154 protected void processInOut(final Exchange exchange) {
155 final org.apache.camel.Message in = exchange.getIn();
156
157 String destinationName = in.getHeader(JmsConstants.JMS_DESTINATION_NAME, String.class);
158 // remove the header so it wont be propagated
159 in.removeHeader(JmsConstants.JMS_DESTINATION_NAME);
160 if (destinationName == null) {
161 destinationName = endpoint.getDestinationName();
162 }
163
164 Destination destination = in.getHeader(JmsConstants.JMS_DESTINATION, Destination.class);
165 // remove the header so it wont be propagated
166 in.removeHeader(JmsConstants.JMS_DESTINATION);
167 if (destination == null) {
168 destination = endpoint.getDestination();
169 }
170 if (destination != null) {
171 // prefer to use destination over destination name
172 destinationName = null;
173 }
174
175 testAndSetRequestor();
176
177 // note due to JMS transaction semantics we cannot use a single transaction
178 // for sending the request and receiving the response
179 final Destination replyTo = requestor.getReplyTo();
180
181 if (replyTo == null) {
182 throw new RuntimeExchangeException("Failed to resolve replyTo destination", exchange);
183 }
184
185 final boolean msgIdAsCorrId = endpoint.getConfiguration().isUseMessageIDAsCorrelationID();
186 String correlationId = in.getHeader("JMSCorrelationID", String.class);
187
188 if (correlationId == null && !msgIdAsCorrId) {
189 in.setHeader("JMSCorrelationID", getUuidGenerator().generateUuid());
190 }
191
192 final ValueHolder<FutureTask> futureHolder = new ValueHolder<FutureTask>();
193 final DeferredMessageSentCallback callback = msgIdAsCorrId ? deferredRequestReplyMap.createDeferredMessageSentCallback() : null;
194
195 MessageCreator messageCreator = new MessageCreator() {
196 public Message createMessage(Session session) throws JMSException {
197 Message message = endpoint.getBinding().makeJmsMessage(exchange, in, session, null);
198 message.setJMSReplyTo(replyTo);
199 requestor.setReplyToSelectorHeader(in, message);
200
201 FutureTask future;
202 future = (!msgIdAsCorrId)
203 ? requestor.getReceiveFuture(message.getJMSCorrelationID(), endpoint.getConfiguration().getRequestTimeout())
204 : requestor.getReceiveFuture(callback);
205
206 futureHolder.set(future);
207 return message;
208 }
209 };
210
211 doSend(true, destinationName, destination, messageCreator, callback);
212
213 // after sending then set the OUT message id to the JMSMessageID so its identical
214 setMessageId(exchange);
215
216 // lets wait and return the response
217 long requestTimeout = endpoint.getConfiguration().getRequestTimeout();
218 try {
219 Message message = null;
220 try {
221 if (LOG.isDebugEnabled()) {
222 LOG.debug("Message sent, now waiting for reply at: " + replyTo.toString());
223 }
224 if (requestTimeout <= 0) {
225 message = (Message)futureHolder.get().get();
226 } else {
227 message = (Message)futureHolder.get().get(requestTimeout, TimeUnit.MILLISECONDS);
228 }
229 } catch (InterruptedException e) {
230 if (LOG.isDebugEnabled()) {
231 LOG.debug("Future interrupted: " + e, e);
232 }
233 } catch (TimeoutException e) {
234 if (LOG.isDebugEnabled()) {
235 LOG.debug("Future timed out: " + e, e);
236 }
237 }
238 if (message != null) {
239 // the response can be an exception
240 JmsMessage response = new JmsMessage(message, endpoint.getBinding());
241 Object body = response.getBody();
242
243 if (endpoint.isTransferException() && body instanceof Exception) {
244 if (LOG.isDebugEnabled()) {
245 LOG.debug("Reply received. Setting reply as an Exception: " + body);
246 }
247 // we got an exception back and endpoint was configured to transfer exception
248 // therefore set response as exception
249 exchange.setException((Exception) body);
250 } else {
251 if (LOG.isDebugEnabled()) {
252 LOG.debug("Reply received. Setting reply as OUT message: " + body);
253 }
254 // regular response
255 exchange.setOut(response);
256 }
257
258 // restore correlation id in case the remote server messed with it
259 if (correlationId != null) {
260 message.setJMSCorrelationID(correlationId);
261 exchange.getOut().setHeader("JMSCorrelationID", correlationId);
262 }
263 } else {
264 // no response, so lets set a timed out exception
265 exchange.setException(new ExchangeTimedOutException(exchange, requestTimeout));
266 }
267 } catch (Exception e) {
268 exchange.setException(e);
269 }
270
271 }
272
273 protected void processInOnly(final Exchange exchange) {
274 final org.apache.camel.Message in = exchange.getIn();
275
276 String destinationName = in.getHeader(JmsConstants.JMS_DESTINATION_NAME, String.class);
277 if (destinationName != null) {
278 // remove the header so it wont be propagated
279 in.removeHeader(JmsConstants.JMS_DESTINATION_NAME);
280 }
281 if (destinationName == null) {
282 destinationName = endpoint.getDestinationName();
283 }
284
285 Destination destination = in.getHeader(JmsConstants.JMS_DESTINATION, Destination.class);
286 if (destination != null) {
287 // remove the header so it wont be propagated
288 in.removeHeader(JmsConstants.JMS_DESTINATION);
289 }
290 if (destination == null) {
291 destination = endpoint.getDestination();
292 }
293 if (destination != null) {
294 // prefer to use destination over destination name
295 destinationName = null;
296 }
297
298 // we must honor these special flags to preserve QoS
299 if (!endpoint.isPreserveMessageQos() && !endpoint.isExplicitQosEnabled()) {
300 Object replyTo = exchange.getIn().getHeader("JMSReplyTo");
301 if (replyTo != null) {
302 // we are routing an existing JmsMessage, origin from another JMS endpoint
303 // then we need to remove the existing JMSReplyTo
304 // as we are not out capable and thus do not expect a reply, and therefore
305 // the consumer of this message we send should not return a reply
306 String to = destinationName != null ? destinationName : "" + destination;
307 LOG.warn("Disabling JMSReplyTo as this Exchange is not OUT capable with JMSReplyTo: " + replyTo
308 + " for destination: " + to + ". Use preserveMessageQos=true to force Camel to keep the JMSReplyTo."
309 + " Exchange: " + exchange);
310 exchange.getIn().setHeader("JMSReplyTo", null);
311 }
312 }
313
314 MessageCreator messageCreator = new MessageCreator() {
315 public Message createMessage(Session session) throws JMSException {
316 return endpoint.getBinding().makeJmsMessage(exchange, in, session, null);
317 }
318 };
319
320 doSend(false, destinationName, destination, messageCreator, null);
321
322 // after sending then set the OUT message id to the JMSMessageID so its identical
323 setMessageId(exchange);
324 }
325
326 /**
327 * Sends the message using the JmsTemplate.
328 *
329 * @param inOut use inOut or inOnly template
330 * @param destinationName the destination name
331 * @param destination the destination (if no name provided)
332 * @param messageCreator the creator to create the javax.jms.Message to send
333 * @param callback optional callback for inOut messages
334 */
335 protected void doSend(boolean inOut, String destinationName, Destination destination,
336 MessageCreator messageCreator, DeferredMessageSentCallback callback) {
337
338 CamelJmsTemplate template = null;
339 CamelJmsTemplate102 template102 = null;
340 if (endpoint.isUseVersion102()) {
341 template102 = (JmsConfiguration.CamelJmsTemplate102) (inOut ? getInOutTemplate() : getInOnlyTemplate());
342 } else {
343 template = (CamelJmsTemplate) (inOut ? getInOutTemplate() : getInOnlyTemplate());
344 }
345
346 if (LOG.isTraceEnabled()) {
347 LOG.trace("Using " + (inOut ? "inOut" : "inOnly") + " jms template to send with API "
348 + (endpoint.isUseVersion102() ? "v1.0.2" : "v1.1"));
349 }
350
351 // destination should be preferred
352 if (destination != null) {
353 if (inOut) {
354 if (template != null) {
355 template.send(destination, messageCreator, callback);
356 } else if (template102 != null) {
357 template102.send(destination, messageCreator, callback);
358 }
359 } else {
360 if (template != null) {
361 template.send(destination, messageCreator);
362 } else if (template102 != null) {
363 template102.send(destination, messageCreator);
364 }
365 }
366 } else if (destinationName != null) {
367 if (inOut) {
368 if (template != null) {
369 template.send(destinationName, messageCreator, callback);
370 } else if (template102 != null) {
371 template102.send(destinationName, messageCreator, callback);
372 }
373 } else {
374 if (template != null) {
375 template.send(destinationName, messageCreator);
376 } else if (template102 != null) {
377 template102.send(destinationName, messageCreator);
378 }
379 }
380 } else {
381 throw new IllegalArgumentException("Neither destination nor destinationName is specified on this endpoint: " + endpoint);
382 }
383 }
384
385 protected void setMessageId(Exchange exchange) {
386 if (exchange.hasOut()) {
387 JmsMessage out = (JmsMessage) exchange.getOut();
388 try {
389 if (out != null && out.getJmsMessage() != null) {
390 out.setMessageId(out.getJmsMessage().getJMSMessageID());
391 }
392 } catch (JMSException e) {
393 LOG.warn("Unable to retrieve JMSMessageID from outgoing "
394 + "JMS Message and set it into Camel's MessageId", e);
395 }
396 }
397 }
398
399 public JmsOperations getInOnlyTemplate() {
400 if (inOnlyTemplate == null) {
401 inOnlyTemplate = endpoint.createInOnlyTemplate();
402 }
403 return inOnlyTemplate;
404 }
405
406 public void setInOnlyTemplate(JmsOperations inOnlyTemplate) {
407 this.inOnlyTemplate = inOnlyTemplate;
408 }
409
410 public JmsOperations getInOutTemplate() {
411 if (inOutTemplate == null) {
412 inOutTemplate = endpoint.createInOutTemplate();
413 }
414 return inOutTemplate;
415 }
416
417 public void setInOutTemplate(JmsOperations inOutTemplate) {
418 this.inOutTemplate = inOutTemplate;
419 }
420
421 public UuidGenerator getUuidGenerator() {
422 if (uuidGenerator == null) {
423 uuidGenerator = UuidGenerator.get();
424 }
425 return uuidGenerator;
426 }
427
428 public void setUuidGenerator(UuidGenerator uuidGenerator) {
429 this.uuidGenerator = uuidGenerator;
430 }
431
432 }