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 }