org.gytheio.messaging.camel.CamelRequestReplyMessageProducer.java Source code

Java tutorial

Introduction

Here is the source code for org.gytheio.messaging.camel.CamelRequestReplyMessageProducer.java

Source

/*
 * Copyright (C) 2005-2014 Alfresco Software Limited.
 *
 * This file is part of Gytheio
 *
 * Gytheio is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Gytheio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Gytheio. If not, see <http://www.gnu.org/licenses/>.
 */
package org.gytheio.messaging.camel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

import org.gytheio.content.ContentIOException;
import org.gytheio.messaging.Reply;
import org.gytheio.messaging.Request;
import org.gytheio.messaging.RequestReplyMessageProducer;

/**
 * An Apache Camel implementation of a request-reply message producer.
 * <p>
 * A pending requests map is maintained and the {@link ReplyCallable} periodically polls
 * that map to determine if a correlated reply has been received.
 * <p>
 * Note that the built-in Camel asynchronous processing was not used for a few reasons:
 * <ul>
 *    <li>Routing of the temp queues back to the appropriate location and custom unmarshalling proved problematic</li>
 *    <li>Several references stated that there could be additional overhead in constructing the temp queues even with caching</li>
 *    <li>Clustering of pending requests would have likely been much more difficult</li>
 * </ul>
 * 
 * @author Ray Gauss II
 *
 * @param <RQ> the request type
 * @param <RP> the reply type
 */
public class CamelRequestReplyMessageProducer<RQ extends Request<RP>, RP extends Reply> extends CamelMessageProducer
        implements RequestReplyMessageProducer<RQ, RP> {
    private static final Log logger = LogFactory.getLog(CamelRequestReplyMessageProducer.class);

    private static final long DEFAULT_PENDING_REQUEST_POLLING_INTERVAL_MS = 500;
    private static final long DEFAULT_PENDING_REQUEST_TIMEOUT_MS = 20000;

    // TODO: In a clustered env this would have to be distributed (Hazelcast) or persisted
    protected Map<String, RP> pendingRequests = new HashMap<String, RP>();

    protected long pollingIntervalMs = DEFAULT_PENDING_REQUEST_POLLING_INTERVAL_MS;
    protected long timeoutMs = DEFAULT_PENDING_REQUEST_TIMEOUT_MS;

    protected ExecutorService executorService;

    /**
     * The pending request polling interval in milliseconds
     * 
     * @param pollingIntervalMs
     */
    public void setPollingIntervalMs(long pollingIntervalMs) {
        this.pollingIntervalMs = pollingIntervalMs;
    }

    /**
     * The pending request timeout in milliseconds
     * 
     * @param timeoutMs
     */
    public void setTimeoutMs(long timeoutMs) {
        this.timeoutMs = timeoutMs;
    }

    /**
     * The executor service to be used for executing the polling callable
     * 
     * @param executorService
     */
    public void setExecutorService(ExecutorService executorService) {
        this.executorService = executorService;
    }

    public void init() {
        if (executorService == null) {
            executorService = Executors.newCachedThreadPool();
        }
    }

    @Override
    public Future<RP> asyncRequest(RQ request) {
        send(request);
        pendingRequests.put(request.getRequestId(), null);

        FutureTask<RP> future = new FutureTask<>(new ReplyCallable(request.getRequestId()));
        executorService.execute(future);
        return future;
    }

    @SuppressWarnings("unchecked")
    public void onReceive(Object message) {
        // TODO Better handling of checking object type and casting
        RP reply = (RP) message;

        if (logger.isDebugEnabled()) {
            logger.debug("Received reply for request " + reply.getRequestId());
        }

        if (!pendingRequests.containsKey(reply.getRequestId())) {
            // TODO Need to better handle errors here, send an error message?
            logger.error("Unknown pending request: " + reply.getRequestId());
            return;
        }

        pendingRequests.put(reply.getRequestId(), reply);
    }

    /**
     * Class which polls the pending requests map to determine if a 
     * correlated reply has been received.
     */
    public class ReplyCallable implements Callable<RP> {
        private String requestId;

        /**
         * Constructor which takes a correlating request ID
         * 
         * @param requestId
         */
        public ReplyCallable(String requestId) {
            this.requestId = requestId;
        }

        @Override
        public RP call() throws Exception {
            long startTime = (new Date()).getTime();
            try {
                while (((new Date()).getTime() - startTime <= timeoutMs) || timeoutMs == -1) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Polling for pending request " + requestId + " completion in "
                                + pollingIntervalMs + "ms...");
                    }
                    Thread.sleep(pollingIntervalMs);
                    RP reply = pendingRequests.get(requestId);

                    if (reply == null) {
                        // reply hasn't come back yet
                        continue;
                    }

                    pendingRequests.remove(requestId);
                    return reply;
                }
                // We must have timed out
                throw new ContentIOException("Timeout while waiting for reply");
            } catch (InterruptedException e) {
                // We were asked to stop
                return null;
            }
        }
    }

}