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.RejectedExecutionException; 020import java.util.concurrent.ScheduledExecutorService; 021import java.util.concurrent.TimeUnit; 022import java.util.concurrent.atomic.AtomicInteger; 023 024import org.apache.camel.AsyncCallback; 025import org.apache.camel.CamelContext; 026import org.apache.camel.Exchange; 027import org.apache.camel.Processor; 028import org.apache.camel.util.ObjectHelper; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032/** 033 * A useful base class for any processor which provides some kind of throttling 034 * or delayed processing. 035 * <p/> 036 * This implementation will block while waiting. 037 * 038 * @version 039 */ 040public abstract class DelayProcessorSupport extends DelegateAsyncProcessor { 041 protected final Logger log = LoggerFactory.getLogger(getClass()); 042 private final CamelContext camelContext; 043 private final ScheduledExecutorService executorService; 044 private final boolean shutdownExecutorService; 045 private boolean asyncDelayed; 046 private boolean callerRunsWhenRejected = true; 047 private final AtomicInteger delayedCount = new AtomicInteger(0); 048 049 // TODO: Add option to cancel tasks on shutdown so we can stop fast 050 051 private final class ProcessCall implements Runnable { 052 private final Exchange exchange; 053 private final AsyncCallback callback; 054 055 ProcessCall(Exchange exchange, AsyncCallback callback) { 056 this.exchange = exchange; 057 this.callback = callback; 058 } 059 060 public void run() { 061 // we are running now so decrement the counter 062 delayedCount.decrementAndGet(); 063 064 log.trace("Delayed task woke up and continues routing for exchangeId: {}", exchange.getExchangeId()); 065 if (!isRunAllowed()) { 066 exchange.setException(new RejectedExecutionException("Run is not allowed")); 067 } 068 069 // process the exchange now that we woke up 070 DelayProcessorSupport.this.processor.process(exchange, new AsyncCallback() { 071 @Override 072 public void done(boolean doneSync) { 073 log.trace("Delayed task done for exchangeId: {}", exchange.getExchangeId()); 074 // we must done the callback from this async callback as well, to ensure callback is done correctly 075 // must invoke done on callback with false, as that is what the original caller would 076 // expect as we returned false in the process method 077 callback.done(false); 078 } 079 }); 080 } 081 } 082 083 public DelayProcessorSupport(CamelContext camelContext, Processor processor) { 084 this(camelContext, processor, null, false); 085 } 086 087 public DelayProcessorSupport(CamelContext camelContext, Processor processor, ScheduledExecutorService executorService, boolean shutdownExecutorService) { 088 super(processor); 089 this.camelContext = camelContext; 090 this.executorService = executorService; 091 this.shutdownExecutorService = shutdownExecutorService; 092 } 093 094 protected boolean processDelay(Exchange exchange, AsyncCallback callback, long delay) { 095 if (!isAsyncDelayed() || exchange.isTransacted()) { 096 // use synchronous delay (also required if using transactions) 097 try { 098 delay(delay, exchange); 099 // then continue routing 100 return processor.process(exchange, callback); 101 } catch (Exception e) { 102 // exception occurred so we are done 103 exchange.setException(e); 104 callback.done(true); 105 return true; 106 } 107 } else { 108 // asynchronous delay so schedule a process call task 109 // and increment the counter (we decrement the counter when we run the ProcessCall) 110 delayedCount.incrementAndGet(); 111 ProcessCall call = new ProcessCall(exchange, callback); 112 try { 113 log.trace("Scheduling delayed task to run in {} millis for exchangeId: {}", 114 delay, exchange.getExchangeId()); 115 executorService.schedule(call, delay, TimeUnit.MILLISECONDS); 116 // tell Camel routing engine we continue routing asynchronous 117 return false; 118 } catch (RejectedExecutionException e) { 119 // we were not allowed to run the ProcessCall, so need to decrement the counter here 120 delayedCount.decrementAndGet(); 121 if (isCallerRunsWhenRejected()) { 122 if (!isRunAllowed()) { 123 exchange.setException(new RejectedExecutionException()); 124 } else { 125 log.debug("Scheduling rejected task, so letting caller run, delaying at first for {} millis for exchangeId: {}", delay, exchange.getExchangeId()); 126 // let caller run by processing 127 try { 128 delay(delay, exchange); 129 } catch (InterruptedException ie) { 130 exchange.setException(ie); 131 } 132 // then continue routing 133 return processor.process(exchange, callback); 134 } 135 } else { 136 exchange.setException(e); 137 } 138 // caller don't run the task so we are done 139 callback.done(true); 140 return true; 141 } 142 } 143 } 144 145 @Override 146 public boolean process(Exchange exchange, AsyncCallback callback) { 147 if (!isRunAllowed()) { 148 exchange.setException(new RejectedExecutionException("Run is not allowed")); 149 callback.done(true); 150 return true; 151 } 152 153 // calculate delay and wait 154 long delay; 155 try { 156 delay = calculateDelay(exchange); 157 if (delay <= 0) { 158 // no delay then continue routing 159 log.trace("No delay for exchangeId: {}", exchange.getExchangeId()); 160 return processor.process(exchange, callback); 161 } 162 } catch (Throwable e) { 163 exchange.setException(e); 164 callback.done(true); 165 return true; 166 } 167 168 return processDelay(exchange, callback, delay); 169 } 170 171 public boolean isAsyncDelayed() { 172 return asyncDelayed; 173 } 174 175 public void setAsyncDelayed(boolean asyncDelayed) { 176 this.asyncDelayed = asyncDelayed; 177 } 178 179 public boolean isCallerRunsWhenRejected() { 180 return callerRunsWhenRejected; 181 } 182 183 public void setCallerRunsWhenRejected(boolean callerRunsWhenRejected) { 184 this.callerRunsWhenRejected = callerRunsWhenRejected; 185 } 186 187 protected abstract long calculateDelay(Exchange exchange); 188 189 /** 190 * Gets the current number of {@link Exchange}s being delayed (hold back due throttle limit hit) 191 */ 192 public int getDelayedCount() { 193 return delayedCount.get(); 194 } 195 196 /** 197 * Delays the given time before continuing. 198 * <p/> 199 * This implementation will block while waiting 200 * 201 * @param delay the delay time in millis 202 * @param exchange the exchange being processed 203 */ 204 protected void delay(long delay, Exchange exchange) throws InterruptedException { 205 // only run is we are started 206 if (!isRunAllowed()) { 207 return; 208 } 209 210 if (delay < 0) { 211 return; 212 } else { 213 try { 214 // keep track on delayer counter while we sleep 215 delayedCount.incrementAndGet(); 216 sleep(delay); 217 } catch (InterruptedException e) { 218 handleSleepInterruptedException(e, exchange); 219 } finally { 220 delayedCount.decrementAndGet(); 221 } 222 } 223 } 224 225 /** 226 * Called when a sleep is interrupted; allows derived classes to handle this case differently 227 */ 228 protected void handleSleepInterruptedException(InterruptedException e, Exchange exchange) throws InterruptedException { 229 if (log.isDebugEnabled()) { 230 log.debug("Sleep interrupted, are we stopping? {}", isStopping() || isStopped()); 231 } 232 Thread.currentThread().interrupt(); 233 throw e; 234 } 235 236 protected long currentSystemTime() { 237 return System.currentTimeMillis(); 238 } 239 240 private void sleep(long delay) throws InterruptedException { 241 if (delay <= 0) { 242 return; 243 } 244 log.trace("Sleeping for: {} millis", delay); 245 Thread.sleep(delay); 246 } 247 248 @Override 249 protected void doStart() throws Exception { 250 if (isAsyncDelayed()) { 251 ObjectHelper.notNull(executorService, "executorService", this); 252 } 253 super.doStart(); 254 } 255 256 @Override 257 protected void doShutdown() throws Exception { 258 if (shutdownExecutorService && executorService != null) { 259 camelContext.getExecutorServiceManager().shutdownNow(executorService); 260 } 261 super.doShutdown(); 262 } 263}