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}