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.component.timer;
018
019import java.util.Date;
020import java.util.Timer;
021import java.util.TimerTask;
022import java.util.concurrent.ExecutorService;
023import java.util.concurrent.atomic.AtomicLong;
024
025import org.apache.camel.AsyncCallback;
026import org.apache.camel.CamelContext;
027import org.apache.camel.Exchange;
028import org.apache.camel.Processor;
029import org.apache.camel.StartupListener;
030import org.apache.camel.Suspendable;
031import org.apache.camel.impl.DefaultConsumer;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * The timer consumer.
037 *
038 * @version 
039 */
040public class TimerConsumer extends DefaultConsumer implements StartupListener, Suspendable {
041    private static final Logger LOG = LoggerFactory.getLogger(TimerConsumer.class);
042    private final TimerEndpoint endpoint;
043    private volatile TimerTask task;
044    private volatile boolean configured;
045    private ExecutorService executorService;
046
047    public TimerConsumer(TimerEndpoint endpoint, Processor processor) {
048        super(endpoint, processor);
049        this.endpoint = endpoint;
050    }
051
052    @Override
053    public TimerEndpoint getEndpoint() {
054        return (TimerEndpoint) super.getEndpoint();
055    }
056
057    @Override
058    protected void doStart() throws Exception {
059        super.doStart();
060
061        if (endpoint.getDelay() >= 0) {
062            task = new TimerTask() {
063                // counter
064                private final AtomicLong counter = new AtomicLong();
065
066                @Override
067                public void run() {
068                    if (!isTaskRunAllowed()) {
069                        // do not run timer task as it was not allowed
070                        LOG.debug("Run not allowed for timer: {}", endpoint);
071                        return;
072                    }
073
074                    try {
075                        long count = counter.incrementAndGet();
076
077                        boolean fire = endpoint.getRepeatCount() <= 0 || count <= endpoint.getRepeatCount();
078                        if (fire) {
079                            sendTimerExchange(count);
080                        } else {
081                            // no need to fire anymore as we exceeded repeat
082                            // count
083                            LOG.debug("Cancelling {} timer as repeat count limit reached after {} counts.", endpoint.getTimerName(), endpoint.getRepeatCount());
084                            cancel();
085                        }
086                    } catch (Throwable e) {
087                        // catch all to avoid the JVM closing the thread and not
088                        // firing again
089                        LOG.warn("Error processing exchange. This exception will be ignored, to let the timer be able to trigger again.", e);
090                    }
091                }
092            };
093
094            // only configure task if CamelContext already started, otherwise
095            // the StartupListener
096            // is configuring the task later
097            if (!configured && endpoint.getCamelContext().getStatus().isStarted()) {
098                Timer timer = endpoint.getTimer(this);
099                configureTask(task, timer);
100            }
101        } else {
102            // if the delay is negative then we use an ExecutorService and fire messages as soon as possible
103            executorService = endpoint.getCamelContext().getExecutorServiceManager().newSingleThreadExecutor(this, endpoint.getEndpointUri());
104
105            executorService.execute(new Runnable() {
106                public void run() {
107                    final AtomicLong counter = new AtomicLong();
108                    long count = counter.incrementAndGet();
109                    while ((endpoint.getRepeatCount() <= 0 || count <= endpoint.getRepeatCount()) && isRunAllowed()) {
110                        sendTimerExchange(count);
111                        count = counter.incrementAndGet();
112                    }
113                }
114            });
115        }
116    }
117
118    @Override
119    protected void doStop() throws Exception {
120        if (task != null) {
121            task.cancel();
122        }
123        task = null;
124        configured = false;
125
126        // remove timer
127        endpoint.removeTimer(this);
128        
129        // if executorService is instantiated then we shutdown it
130        if (executorService != null) {
131            endpoint.getCamelContext().getExecutorServiceManager().shutdown(executorService);
132            executorService = null;
133        }
134
135        super.doStop();
136    }
137
138    @Override
139    public void onCamelContextStarted(CamelContext context, boolean alreadyStarted) throws Exception {
140        if (task != null && !configured) {
141            Timer timer = endpoint.getTimer(this);
142            configureTask(task, timer);
143        } 
144    }
145
146    /**
147     * Whether the timer task is allow to run or not
148     */
149    protected boolean isTaskRunAllowed() {
150        // only allow running the timer task if we can run and are not suspended,
151        // and CamelContext must have been fully started
152        return endpoint.getCamelContext().getStatus().isStarted() && isRunAllowed() && !isSuspended();
153    }
154
155    protected void configureTask(TimerTask task, Timer timer) {
156        if (endpoint.isFixedRate()) {
157            if (endpoint.getTime() != null) {
158                timer.scheduleAtFixedRate(task, endpoint.getTime(), endpoint.getPeriod());
159            } else {
160                timer.scheduleAtFixedRate(task, endpoint.getDelay(), endpoint.getPeriod());
161            }
162        } else {
163            if (endpoint.getTime() != null) {
164                if (endpoint.getPeriod() > 0) {
165                    timer.schedule(task, endpoint.getTime(), endpoint.getPeriod());
166                } else {
167                    timer.schedule(task, endpoint.getTime());
168                }
169            } else {
170                if (endpoint.getPeriod() > 0) {
171                    timer.schedule(task, endpoint.getDelay(), endpoint.getPeriod());
172                } else {
173                    timer.schedule(task, endpoint.getDelay());
174                }
175            }
176        }
177        configured = true;
178    }
179
180    protected void sendTimerExchange(long counter) {
181        final Exchange exchange = endpoint.createExchange();
182        exchange.setProperty(Exchange.TIMER_COUNTER, counter);
183        exchange.setProperty(Exchange.TIMER_NAME, endpoint.getTimerName());
184        exchange.setProperty(Exchange.TIMER_TIME, endpoint.getTime());
185        exchange.setProperty(Exchange.TIMER_PERIOD, endpoint.getPeriod());
186
187        Date now = new Date();
188        exchange.setProperty(Exchange.TIMER_FIRED_TIME, now);
189        // also set now on in header with same key as quartz to be consistent
190        exchange.getIn().setHeader("firedTime", now);
191
192        if (LOG.isTraceEnabled()) {
193            LOG.trace("Timer {} is firing #{} count", endpoint.getTimerName(), counter);
194        }
195
196        if (!endpoint.isSynchronous()) {
197            getAsyncProcessor().process(exchange, new AsyncCallback() {
198                @Override
199                public void done(boolean doneSync) {
200                    // handle any thrown exception
201                    if (exchange.getException() != null) {
202                        getExceptionHandler().handleException("Error processing exchange", exchange, exchange.getException());
203                    }
204                }
205            });
206        } else {
207            try {
208                getProcessor().process(exchange);
209            } catch (Exception e) {
210                exchange.setException(e);
211            }
212
213            // handle any thrown exception
214            if (exchange.getException() != null) {
215                getExceptionHandler().handleException("Error processing exchange", exchange, exchange.getException());
216            }
217        }
218    }
219}