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    }