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 */ 017package org.apache.camel.processor; 018 019import java.util.concurrent.ExecutorService; 020import java.util.concurrent.RejectedExecutionException; 021import java.util.concurrent.ThreadPoolExecutor; 022import java.util.concurrent.atomic.AtomicBoolean; 023 024import org.apache.camel.AsyncCallback; 025import org.apache.camel.AsyncProcessor; 026import org.apache.camel.CamelContext; 027import org.apache.camel.Exchange; 028import org.apache.camel.Rejectable; 029import org.apache.camel.ThreadPoolRejectedPolicy; 030import org.apache.camel.spi.IdAware; 031import org.apache.camel.support.ServiceSupport; 032import org.apache.camel.util.AsyncProcessorHelper; 033import org.apache.camel.util.ObjectHelper; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037/** 038 * Threads processor that leverage a thread pool for continue processing the {@link Exchange}s 039 * using the asynchronous routing engine. 040 * <p/> 041 * <b>Notice:</b> For transacted routes then this {@link ThreadsProcessor} is not in use, as we want to 042 * process messages using the same thread to support all work done in the same transaction. The reason 043 * is that the transaction manager that orchestrate the transaction, requires all the work to be done 044 * on the same thread. 045 * <p/> 046 * Pay attention to how this processor handles rejected tasks. 047 * <ul> 048 * <li>Abort - The current exchange will be set with a {@link RejectedExecutionException} exception, 049 * and marked to stop continue routing. 050 * The {@link org.apache.camel.spi.UnitOfWork} will be regarded as <b>failed</b>, due the exception.</li> 051 * <li>Discard - The current exchange will be marked to stop continue routing (notice no exception is set). 052 * The {@link org.apache.camel.spi.UnitOfWork} will be regarded as <b>successful</b>, due no exception being set.</li> 053 * <li>DiscardOldest - The oldest exchange will be marked to stop continue routing (notice no exception is set). 054 * The {@link org.apache.camel.spi.UnitOfWork} will be regarded as <b>successful</b>, due no exception being set. 055 * And the current exchange will be added to the task queue.</li> 056 * <li>CallerRuns - The current exchange will be processed by the current thread. Which mean the current thread 057 * will not be free to process a new exchange, as its processing the current exchange.</li> 058 * </ul> 059 */ 060public class ThreadsProcessor extends ServiceSupport implements AsyncProcessor, IdAware { 061 062 private static final Logger LOG = LoggerFactory.getLogger(ThreadsProcessor.class); 063 private String id; 064 private final CamelContext camelContext; 065 private final ExecutorService executorService; 066 private final ThreadPoolRejectedPolicy rejectedPolicy; 067 private volatile boolean shutdownExecutorService; 068 private final AtomicBoolean shutdown = new AtomicBoolean(true); 069 070 private final class ProcessCall implements Runnable, Rejectable { 071 private final Exchange exchange; 072 private final AsyncCallback callback; 073 private final boolean done; 074 075 ProcessCall(Exchange exchange, AsyncCallback callback, boolean done) { 076 this.exchange = exchange; 077 this.callback = callback; 078 this.done = done; 079 } 080 081 @Override 082 public void run() { 083 LOG.trace("Continue routing exchange {}", exchange); 084 if (shutdown.get()) { 085 exchange.setException(new RejectedExecutionException("ThreadsProcessor is not running.")); 086 } 087 callback.done(done); 088 } 089 090 @Override 091 public void reject() { 092 // reject should mark the exchange with an rejected exception and mark not to route anymore 093 exchange.setException(new RejectedExecutionException()); 094 LOG.trace("Rejected routing exchange {}", exchange); 095 if (shutdown.get()) { 096 exchange.setException(new RejectedExecutionException("ThreadsProcessor is not running.")); 097 } 098 callback.done(done); 099 } 100 101 @Override 102 public String toString() { 103 return "ProcessCall[" + exchange + "]"; 104 } 105 } 106 107 public ThreadsProcessor(CamelContext camelContext, ExecutorService executorService, boolean shutdownExecutorService, ThreadPoolRejectedPolicy rejectedPolicy) { 108 ObjectHelper.notNull(camelContext, "camelContext"); 109 ObjectHelper.notNull(executorService, "executorService"); 110 ObjectHelper.notNull(rejectedPolicy, "rejectedPolicy"); 111 this.camelContext = camelContext; 112 this.executorService = executorService; 113 this.shutdownExecutorService = shutdownExecutorService; 114 this.rejectedPolicy = rejectedPolicy; 115 } 116 117 public void process(final Exchange exchange) throws Exception { 118 AsyncProcessorHelper.process(this, exchange); 119 } 120 121 public boolean process(Exchange exchange, AsyncCallback callback) { 122 if (shutdown.get()) { 123 throw new IllegalStateException("ThreadsProcessor is not running."); 124 } 125 126 // we cannot execute this asynchronously for transacted exchanges, as the transaction manager doesn't support 127 // using different threads in the same transaction 128 if (exchange.isTransacted()) { 129 LOG.trace("Transacted Exchange must be routed synchronously for exchangeId: {} -> {}", exchange.getExchangeId(), exchange); 130 callback.done(true); 131 return true; 132 } 133 134 try { 135 // process the call in asynchronous mode 136 ProcessCall call = new ProcessCall(exchange, callback, false); 137 LOG.trace("Submitting task {}", call); 138 executorService.submit(call); 139 // tell Camel routing engine we continue routing asynchronous 140 return false; 141 } catch (Throwable e) { 142 if (executorService instanceof ThreadPoolExecutor) { 143 ThreadPoolExecutor tpe = (ThreadPoolExecutor) executorService; 144 // process the call in synchronous mode 145 ProcessCall call = new ProcessCall(exchange, callback, true); 146 rejectedPolicy.asRejectedExecutionHandler().rejectedExecution(call, tpe); 147 return true; 148 } else { 149 exchange.setException(e); 150 callback.done(true); 151 return true; 152 } 153 } 154 } 155 156 public ExecutorService getExecutorService() { 157 return executorService; 158 } 159 160 public String toString() { 161 return "Threads"; 162 } 163 164 public String getId() { 165 return id; 166 } 167 168 public void setId(String id) { 169 this.id = id; 170 } 171 172 public ThreadPoolRejectedPolicy getRejectedPolicy() { 173 return rejectedPolicy; 174 } 175 176 protected void doStart() throws Exception { 177 shutdown.set(false); 178 } 179 180 protected void doStop() throws Exception { 181 shutdown.set(true); 182 } 183 184 protected void doShutdown() throws Exception { 185 if (shutdownExecutorService) { 186 camelContext.getExecutorServiceManager().shutdownNow(executorService); 187 } 188 super.doShutdown(); 189 } 190 191}