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.processor;
018    
019    import java.util.ArrayList;
020    import java.util.List;
021    import java.util.concurrent.BlockingQueue;
022    import java.util.concurrent.Callable;
023    import java.util.concurrent.ExecutorService;
024    import java.util.concurrent.LinkedBlockingQueue;
025    import java.util.concurrent.TimeUnit;
026    
027    import org.apache.camel.AsyncCallback;
028    import org.apache.camel.AsyncProcessor;
029    import org.apache.camel.Endpoint;
030    import org.apache.camel.Exchange;
031    import org.apache.camel.ExchangePattern;
032    import org.apache.camel.Navigate;
033    import org.apache.camel.Processor;
034    import org.apache.camel.Producer;
035    import org.apache.camel.ProducerCallback;
036    import org.apache.camel.impl.LoggingExceptionHandler;
037    import org.apache.camel.spi.ExceptionHandler;
038    import org.apache.camel.util.ExchangeHelper;
039    import org.apache.camel.util.concurrent.ExecutorServiceHelper;
040    
041    /**
042     * @version $Revision: 889642 $
043     */
044    public class SendAsyncProcessor extends SendProcessor implements Runnable, Navigate<Processor> {
045    
046        private static final int DEFAULT_THREADPOOL_SIZE = 10;
047        protected final Processor target;
048        protected final BlockingQueue<Exchange> completedTasks = new LinkedBlockingQueue<Exchange>();
049        protected ExecutorService executorService;
050        protected ExecutorService producerExecutorService;
051        protected int poolSize = DEFAULT_THREADPOOL_SIZE;
052        protected ExceptionHandler exceptionHandler;
053    
054        public SendAsyncProcessor(Endpoint destination, Processor target) {
055            super(destination);
056            this.target = target;
057        }
058    
059        public SendAsyncProcessor(Endpoint destination, ExchangePattern pattern, Processor target) {
060            super(destination, pattern);
061            this.target = target;
062        }
063    
064        @Override
065        protected Exchange configureExchange(Exchange exchange, ExchangePattern pattern) {
066            // use a new copy of the exchange to route async and handover the on completion to the new copy
067            // so its the new copy that performs the on completion callback when its done
068            final Exchange copy = ExchangeHelper.createCorrelatedCopy(exchange, true);
069            if (pattern != null) {
070                copy.setPattern(pattern);
071            } else {
072                // default to use in out as we do request reply over async
073                copy.setPattern(ExchangePattern.InOut);
074            }
075            // configure the endpoint we are sending to
076            copy.setProperty(Exchange.TO_ENDPOINT, destination.getEndpointUri());
077            // send the copy
078            return copy;
079        }
080    
081        @Override
082        public Exchange doProcess(Exchange exchange) throws Exception {
083            // now we are done, we should have a API callback for this
084            // send the exchange to the destination using a producer
085            Exchange answer = getProducerCache(exchange).doInProducer(destination, exchange, pattern, new ProducerCallback<Exchange>() {
086                public Exchange doInProducer(Producer producer, Exchange exchange, ExchangePattern pattern) throws Exception {
087                    exchange = configureExchange(exchange, pattern);
088    
089                    // pass in the callback that adds the exchange to the completed list of tasks
090                    final AsyncCallback callback = new AsyncCallback() {
091                        public void onTaskCompleted(Exchange exchange) {
092                            completedTasks.add(exchange);
093                        }
094                    };
095    
096                    if (producer instanceof AsyncProcessor) {
097                        // producer is async capable so let it process it directly
098                        doAsyncProcess((AsyncProcessor) producer, exchange, callback);
099                    } else {
100                        // producer is a regular processor so simulate async behaviour
101                        doSimulateAsyncProcess(producer, exchange, callback);
102                    }
103    
104                    // and return the exchange
105                    return exchange;
106                }
107            });
108    
109            return answer;
110        }
111    
112        /**
113         * The producer is already capable of async processing so let it process it directly.
114         *
115         * @param producer the async producer
116         * @param exchange the exchange
117         * @param callback the callback
118         *
119         * @throws Exception can be thrown in case of processing errors
120         */
121        protected void doAsyncProcess(AsyncProcessor producer, Exchange exchange, AsyncCallback callback) throws Exception {
122            producer.process(exchange, callback);
123        }
124    
125        /**
126         * The producer is <b>not</b> capable of async processing so lets simulate this by transfering the task
127         * to another {@link ExecutorService} for async processing.
128         *
129         * @param producer the producer
130         * @param exchange the exchange
131         * @param callback the callback
132         *
133         * @throws Exception can be thrown in case of processing errors
134         */
135        protected void doSimulateAsyncProcess(final Processor producer, final Exchange exchange, final AsyncCallback callback) throws Exception {
136            if (LOG.isTraceEnabled()) {
137                LOG.trace("Producer " + producer + " is not an instanceof AsyncProcessor"
138                    + ". Will fallback to simulate async behavior by transferring task to a producer thread pool for further processing.");
139            }
140    
141            // let the producer thread pool handle the task of sending the request which then will simulate the async
142            // behavior as the original thread is not blocking while we wait for the reply
143            getProducerExecutorService().submit(new Callable<Exchange>() {
144                public Exchange call() throws Exception {
145                    // convert the async producer which just blocks until the task is complete
146                    try {
147                        AsyncProcessor asyncProducer = exchange.getContext().getTypeConverter().convertTo(AsyncProcessor.class, producer);
148                        asyncProducer.process(exchange, callback);
149                    } catch (Exception e) {
150                        if (LOG.isDebugEnabled()) {
151                            LOG.debug("Caught exception while processing: " + exchange, e);
152                        }
153                        // set the exception on the exchange so Camel error handling can deal with it
154                        exchange.setException(e);
155                    }
156                    return exchange;
157                }
158            });
159        }
160    
161        @Override
162        public String toString() {
163            return "sendAsyncTo(" + destination + (pattern != null ? " " + pattern : "") + " -> " + target + ")";
164        }
165    
166        public ExecutorService getExecutorService() {
167            if (executorService == null) {
168                executorService = createExecutorService("SendAsyncProcessor-Consumer");
169            }
170            return executorService;
171        }
172    
173        /**
174         * Sets the {@link java.util.concurrent.ExecutorService} to use for consuming replies.
175         *
176         * @param executorService the custom executor service
177         */
178        public void setExecutorService(ExecutorService executorService) {
179            this.executorService = executorService;
180        }
181    
182        public ExecutorService getProducerExecutorService() {
183            if (producerExecutorService == null) {
184                // use a cached pool for the producers which can grow/schrink itself
185                producerExecutorService = ExecutorServiceHelper.newCachedThreadPool("SendAsyncProcessor-Producer", true);
186            }
187            return producerExecutorService;
188        }
189    
190        /**
191         * Sets the {@link java.util.concurrent.ExecutorService} to use for simulating async producers
192         * by transferring the {@link Exchange} to this {@link java.util.concurrent.ExecutorService} for
193         * sending the request and block while waiting for the reply. However the original thread
194         * will not block and as such it all appears as real async request/reply mechanism.
195         *
196         * @param producerExecutorService the custom executor service for producers
197         */
198        public void setProducerExecutorService(ExecutorService producerExecutorService) {
199            this.producerExecutorService = producerExecutorService;
200        }
201    
202        public int getPoolSize() {
203            return poolSize;
204        }
205    
206        public void setPoolSize(int poolSize) {
207            this.poolSize = poolSize;
208        }
209    
210        public ExceptionHandler getExceptionHandler() {
211            if (exceptionHandler == null) {
212                exceptionHandler = new LoggingExceptionHandler(getClass());
213            }
214            return exceptionHandler;
215        }
216    
217        public void setExceptionHandler(ExceptionHandler exceptionHandler) {
218            this.exceptionHandler = exceptionHandler;
219        }
220    
221        public boolean hasNext() {
222            return target != null;
223        }
224    
225        public List<Processor> next() {
226            if (!hasNext()) {
227                return null;
228            }
229            List<Processor> answer = new ArrayList<Processor>(1);
230            answer.add(target);
231            return answer;
232        }
233    
234        public void run() {
235            while (isRunAllowed()) {
236                Exchange exchange;
237                try {
238                    exchange = completedTasks.poll(1000, TimeUnit.MILLISECONDS);
239                } catch (InterruptedException e) {
240                    if (LOG.isDebugEnabled()) {
241                        LOG.debug("Sleep interrupted, are we stopping? " + (isStopping() || isStopped()));
242                    }
243                    continue;
244                }
245    
246                if (exchange != null) {
247                    try {
248                        // copy OUT to IN
249                        if (exchange.hasOut()) {
250                            // replace OUT with IN as async processing changed something
251                            exchange.setIn(exchange.getOut());
252                            exchange.setOut(null);
253                        }
254    
255                        if (LOG.isDebugEnabled()) {
256                            LOG.debug("Async reply received now routing the Exchange: " + exchange);
257                        }
258                        target.process(exchange);
259                    } catch (Exception e) {
260                        getExceptionHandler().handleException(e);
261                    }
262                }
263            }
264        }
265    
266        protected ExecutorService createExecutorService(String name) {
267            return ExecutorServiceHelper.newScheduledThreadPool(poolSize, name, true);
268        }
269    
270        protected void doStart() throws Exception {
271            super.doStart();
272    
273            for (int i = 0; i < poolSize; i++) {
274                getExecutorService().execute(this);
275            }
276        }
277    
278        protected void doStop() throws Exception {
279            super.doStop();
280    
281            if (producerExecutorService != null) {
282                producerExecutorService.shutdownNow();
283                producerExecutorService = null;
284            }
285            if (executorService != null) {
286                executorService.shutdownNow();
287                executorService = null;
288            }
289            completedTasks.clear();
290    
291        }
292    
293    }