/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.component.sjms.producer; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.jms.Connection; import javax.jms.Destination; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.Session; import org.apache.camel.AsyncCallback; import org.apache.camel.CamelException; import org.apache.camel.Exchange; import org.apache.camel.component.sjms.MessageConsumerResources; import org.apache.camel.component.sjms.MessageProducerResources; import org.apache.camel.component.sjms.SjmsEndpoint; import org.apache.camel.component.sjms.SjmsMessage; import org.apache.camel.component.sjms.SjmsProducer; import org.apache.camel.component.sjms.jms.ConnectionResource; import org.apache.camel.component.sjms.jms.JmsConstants; import org.apache.camel.component.sjms.jms.JmsMessageHelper; import org.apache.camel.spi.UuidGenerator; import org.apache.camel.util.ObjectHelper; import org.apache.commons.pool.BasePoolableObjectFactory; import org.apache.commons.pool.impl.GenericObjectPool; /** * A Camel Producer that provides the InOut Exchange pattern. */ public class InOutProducer extends SjmsProducer { private static final Map<String, Exchanger<Object>> EXCHANGERS = new ConcurrentHashMap<String, Exchanger<Object>>(); private static final String GENERATED_CORRELATION_ID_PREFIX = "Camel-"; private UuidGenerator uuidGenerator; private GenericObjectPool<MessageConsumerResources> consumers; public InOutProducer(final SjmsEndpoint endpoint) { super(endpoint); } public UuidGenerator getUuidGenerator() { return uuidGenerator; } public void setUuidGenerator(UuidGenerator uuidGenerator) { this.uuidGenerator = uuidGenerator; } /** * A pool of {@link MessageConsumerResources} objects that are the reply consumers. */ protected class MessageConsumerResourcesFactory extends BasePoolableObjectFactory<MessageConsumerResources> { @Override public MessageConsumerResources makeObject() throws Exception { MessageConsumerResources answer; ConnectionResource connectionResource = getOrCreateConnectionResource(); Connection conn = connectionResource.borrowConnection(); try { Session session; if (isEndpointTransacted()) { session = conn.createSession(true, Session.SESSION_TRANSACTED); } else { session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); } Destination replyToDestination; if (ObjectHelper.isEmpty(getNamedReplyTo())) { replyToDestination = getEndpoint().getDestinationCreationStrategy().createTemporaryDestination(session, isTopic()); } else { replyToDestination = getEndpoint().getDestinationCreationStrategy().createDestination(session, getNamedReplyTo(), isTopic()); } MessageConsumer messageConsumer = getEndpoint().getJmsObjectFactory().createMessageConsumer(session, replyToDestination, null, isTopic(), null, true, false, false); messageConsumer.setMessageListener(new MessageListener() { @Override public void onMessage(final Message message) { log.debug("Message Received in the Consumer Pool"); log.debug(" Message : {}", message); try { Exchanger<Object> exchanger = EXCHANGERS.get(message.getJMSCorrelationID()); exchanger.exchange(message, getResponseTimeOut(), TimeUnit.MILLISECONDS); } catch (Exception e) { log.error("Unable to exchange message: {}", message, e); } } }); answer = new MessageConsumerResources(session, messageConsumer, replyToDestination); } catch (Exception e) { log.error("Unable to create the MessageConsumerResource: " + e.getLocalizedMessage()); throw new CamelException(e); } finally { connectionResource.returnConnection(conn); } return answer; } @Override public void destroyObject(MessageConsumerResources model) throws Exception { if (model.getMessageConsumer() != null) { model.getMessageConsumer().close(); } if (model.getSession() != null) { if (model.getSession().getTransacted()) { try { model.getSession().rollback(); } catch (Exception e) { // Do nothing. Just make sure we are cleaned up } } model.getSession().close(); } } } @Override protected void doStart() throws Exception { if (isEndpointTransacted()) { throw new IllegalArgumentException("InOut exchange pattern is incompatible with transacted=true as it cuases a deadlock. Please use transacted=false or InOnly exchange pattern."); } if (ObjectHelper.isEmpty(getNamedReplyTo())) { log.debug("No reply to destination is defined. Using temporary destinations."); } else { log.debug("Using {} as the reply to destination.", getNamedReplyTo()); } if (uuidGenerator == null) { // use the generator configured on the camel context uuidGenerator = getEndpoint().getCamelContext().getUuidGenerator(); } if (consumers == null) { consumers = new GenericObjectPool<MessageConsumerResources>(new MessageConsumerResourcesFactory()); consumers.setMaxActive(getConsumerCount()); consumers.setMaxIdle(getConsumerCount()); while (consumers.getNumIdle() < consumers.getMaxIdle()) { consumers.addObject(); } } super.doStart(); } @Override protected void doStop() throws Exception { super.doStop(); if (consumers != null) { consumers.close(); consumers = null; } } /** * TODO time out is actually double as it waits for the producer and then * waits for the response. Use an atomic long to manage the countdown */ @Override public void sendMessage(final Exchange exchange, final AsyncCallback callback, final MessageProducerResources producer, final ReleaseProducerCallback releaseProducerCallback) throws Exception { Message request = getEndpoint().getBinding().makeJmsMessage(exchange, producer.getSession()); String correlationId = exchange.getIn().getHeader(JmsConstants.JMS_CORRELATION_ID, String.class); if (correlationId == null) { // we append the 'Camel-' prefix to know it was generated by us correlationId = GENERATED_CORRELATION_ID_PREFIX + getUuidGenerator().generateUuid(); } Object responseObject = null; Exchanger<Object> messageExchanger = new Exchanger<Object>(); JmsMessageHelper.setCorrelationId(request, correlationId); EXCHANGERS.put(correlationId, messageExchanger); MessageConsumerResources consumer = consumers.borrowObject(); JmsMessageHelper.setJMSReplyTo(request, consumer.getReplyToDestination()); consumers.returnObject(consumer); producer.getMessageProducer().send(request); // Return the producer to the pool so another waiting producer // can move forward // without waiting on us to complete the exchange try { releaseProducerCallback.release(producer); } catch (Exception exception) { // thrown if the pool is full. safe to ignore. } try { responseObject = messageExchanger.exchange(null, getResponseTimeOut(), TimeUnit.MILLISECONDS); EXCHANGERS.remove(correlationId); } catch (InterruptedException e) { log.debug("Exchanger was interrupted while waiting on response", e); exchange.setException(e); } catch (TimeoutException e) { log.debug("Exchanger timed out while waiting on response", e); exchange.setException(e); } if (exchange.getException() == null) { if (responseObject instanceof Throwable) { exchange.setException((Throwable) responseObject); } else if (responseObject instanceof Message) { Message message = (Message) responseObject; SjmsMessage response = new SjmsMessage(message, consumer.getSession(), getEndpoint().getBinding()); // the JmsBinding is designed to be "pull-based": it will populate the Camel message on demand // therefore, we link Exchange and OUT message before continuing, so that the JmsBinding has full access // to everything it may need, and can populate headers, properties, etc. accordingly (solves CAMEL-6218). exchange.setOut(response); } else { exchange.setException(new CamelException("Unknown response type: " + responseObject)); } } callback.done(isSynchronous()); } }