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