org.apache.camel.component.jms.reply.ReplyManagerSupport.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.camel.component.jms.reply.ReplyManagerSupport.java

Source

/**
 * 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.jms.reply;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;

import org.apache.camel.AsyncCallback;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangeTimedOutException;
import org.apache.camel.component.jms.JmsEndpoint;
import org.apache.camel.component.jms.JmsMessage;
import org.apache.camel.component.jms.JmsMessageHelper;
import org.apache.camel.impl.ServiceSupport;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.ServiceHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jms.listener.AbstractMessageListenerContainer;

/**
 * Base class for {@link ReplyManager} implementations.
 *
 * @version $Revision$
 */
public abstract class ReplyManagerSupport extends ServiceSupport implements ReplyManager {

    protected final Log log = LogFactory.getLog(getClass());
    protected ScheduledExecutorService executorService;
    protected JmsEndpoint endpoint;
    protected Destination replyTo;
    protected AbstractMessageListenerContainer listenerContainer;
    protected final CountDownLatch replyToLatch = new CountDownLatch(1);
    protected final long replyToTimeout = 10000;
    protected CorrelationMap correlation;

    public void setScheduledExecutorService(ScheduledExecutorService executorService) {
        this.executorService = executorService;
    }

    public void setEndpoint(JmsEndpoint endpoint) {
        this.endpoint = endpoint;
    }

    public void setReplyTo(Destination replyTo) {
        if (log.isTraceEnabled()) {
            log.trace("ReplyTo destination: " + replyTo);
        }
        this.replyTo = replyTo;
        // trigger latch as the reply to has been resolved and set
        replyToLatch.countDown();
    }

    public Destination getReplyTo() {
        if (replyTo != null) {
            return replyTo;
        }
        try {
            // the reply to destination has to be resolved using a DestinationResolver using
            // the MessageListenerContainer which occurs asynchronously so we have to wait
            // for that to happen before we can retrieve the reply to destination to be used
            log.trace("Waiting for replyTo to be set");
            boolean done = replyToLatch.await(replyToTimeout, TimeUnit.MILLISECONDS);
            if (!done) {
                log.warn("ReplyTo destination was not set and timeout occurred");
            } else {
                log.trace("Waiting for replyTo to be set done");
            }
        } catch (InterruptedException e) {
            // ignore
        }
        return replyTo;
    }

    public void onMessage(Message message) {
        String correlationID = null;
        try {
            correlationID = message.getJMSCorrelationID();
        } catch (JMSException e) {
            // ignore
        }
        if (correlationID == null) {
            log.warn("Ignoring message with no correlationID: " + message);
            return;
        }

        if (log.isDebugEnabled()) {
            log.debug("Received reply message with correlationID: " + correlationID + " -> " + message);
        }

        // handle the reply message
        handleReplyMessage(correlationID, message);
    }

    public void processReply(ReplyHolder holder) {
        if (holder != null && isRunAllowed()) {
            Exchange exchange = holder.getExchange();
            Message message = holder.getMessage();

            boolean timeout = holder.isTimeout();
            if (timeout) {
                // no response, so lets set a timed out exception
                exchange.setException(new ExchangeTimedOutException(exchange, holder.getRequestTimeout()));
            } else {
                JmsMessage response = new JmsMessage(message, endpoint.getBinding());
                Object body = response.getBody();

                if (endpoint.isTransferException() && body instanceof Exception) {
                    if (log.isDebugEnabled()) {
                        log.debug("Reply received. Setting reply as an Exception: " + body);
                    }
                    // we got an exception back and endpoint was configured to transfer exception
                    // therefore set response as exception
                    exchange.setException((Exception) body);
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Reply received. Setting reply as OUT message: " + body);
                    }
                    // regular response
                    exchange.setOut(response);
                }

                // restore correlation id in case the remote server messed with it
                if (holder.getOriginalCorrelationId() != null) {
                    JmsMessageHelper.setCorrelationId(message, holder.getOriginalCorrelationId());
                    exchange.getOut().setHeader("JMSCorrelationID", holder.getOriginalCorrelationId());
                }
            }

            // notify callback
            AsyncCallback callback = holder.getCallback();
            callback.done(false);
        }
    }

    protected abstract void handleReplyMessage(String correlationID, Message message);

    protected abstract AbstractMessageListenerContainer createListenerContainer() throws Exception;

    /**
     * <b>IMPORTANT:</b> This logic is only being used due to high performance in-memory only
     * testing using InOut over JMS. Its unlikely to happen in a real life situation with communication
     * to a remote broker, which always will be slower to send back reply, before Camel had a chance
     * to update it's internal correlation map.
     */
    protected ReplyHandler waitForProvisionCorrelationToBeUpdated(String correlationID, Message message) {
        // race condition, when using messageID as correlationID then we store a provisional correlation id
        // at first, which gets updated with the JMSMessageID after the message has been sent. And in the unlikely
        // event that the reply comes back really really fast, and the correlation map hasn't yet been updated
        // from the provisional id to the JMSMessageID. If so we have to wait a bit and lookup again.
        if (log.isWarnEnabled()) {
            log.warn("Early reply received with correlationID [" + correlationID + "] -> " + message);
        }

        ReplyHandler answer = null;

        // wait up till 5 seconds
        boolean done = false;
        int counter = 0;
        while (!done && counter++ < 50) {
            if (log.isTraceEnabled()) {
                log.trace("Early reply not found handler at attempt " + counter + ". Waiting a bit longer.");
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // ignore
            }

            // try again
            answer = correlation.get(correlationID);
            done = answer != null;

            if (answer != null) {
                if (log.isTraceEnabled()) {
                    log.trace("Early reply with correlationID [" + correlationID + "] has been matched after "
                            + counter + " attempts and can be processed using handler: " + answer);
                }
            }
        }

        return answer;
    }

    @Override
    protected void doStart() throws Exception {
        ObjectHelper.notNull(executorService, "executorService", this);
        ObjectHelper.notNull(endpoint, "endpoint", this);

        // purge for timeout every second
        correlation = new CorrelationMap(executorService, 1000);
        ServiceHelper.startService(correlation);

        // create JMS listener and start it
        listenerContainer = createListenerContainer();
        listenerContainer.afterPropertiesSet();
        log.info("Starting reply listener container on endpoint: " + endpoint);
        listenerContainer.start();
    }

    @Override
    protected void doStop() throws Exception {
        ServiceHelper.stopService(correlation);

        if (listenerContainer != null) {
            log.info("Stopping reply listener container on endpoint: " + endpoint);
            listenerContainer.stop();
            listenerContainer.destroy();
            listenerContainer = null;
        }
    }

}