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.impl;
018
019import java.util.HashMap;
020import java.util.Map;
021import java.util.concurrent.ScheduledExecutorService;
022import java.util.concurrent.TimeUnit;
023
024import org.apache.camel.CamelContext;
025import org.apache.camel.Component;
026import org.apache.camel.LoggingLevel;
027import org.apache.camel.PollingConsumer;
028import org.apache.camel.ResolveEndpointFailedException;
029import org.apache.camel.spi.PollingConsumerPollStrategy;
030import org.apache.camel.spi.ScheduledPollConsumerScheduler;
031import org.apache.camel.spi.UriParam;
032import org.apache.camel.util.CamelContextHelper;
033import org.apache.camel.util.EndpointHelper;
034import org.apache.camel.util.IntrospectionSupport;
035
036/**
037 * A base class for {@link org.apache.camel.Endpoint} which creates a {@link ScheduledPollConsumer}
038 *
039 * @version
040 */
041public abstract class ScheduledPollEndpoint extends DefaultEndpoint {
042
043    private static final String SPRING_SCHEDULER = "org.apache.camel.spring.pollingconsumer.SpringScheduledPollConsumerScheduler";
044    private static final String QUARTZ_2_SCHEDULER = "org.apache.camel.pollconsumer.quartz2.QuartzScheduledPollConsumerScheduler";
045
046    // if adding more options then align with org.apache.camel.impl.ScheduledPollConsumer
047    @UriParam(optionalPrefix = "consumer.", defaultValue = "true", label = "consumer,scheduler",
048            description = "Whether the scheduler should be auto started.")
049    private boolean startScheduler = true;
050    @UriParam(optionalPrefix = "consumer.", defaultValue = "1000", label = "consumer,scheduler",
051            description = "Milliseconds before the first poll starts."
052                    + " You can also specify time values using units, such as 60s (60 seconds), 5m30s (5 minutes and 30 seconds), and 1h (1 hour).")
053    private long initialDelay = 1000;
054    @UriParam(optionalPrefix = "consumer.", defaultValue = "500", label = "consumer,scheduler",
055            description = "Milliseconds before the next poll."
056                    + " You can also specify time values using units, such as 60s (60 seconds), 5m30s (5 minutes and 30 seconds), and 1h (1 hour).")
057    private long delay = 500;
058    @UriParam(optionalPrefix = "consumer.", defaultValue = "MILLISECONDS", label = "consumer,scheduler",
059            description = "Time unit for initialDelay and delay options.")
060    private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
061    @UriParam(optionalPrefix = "consumer.", defaultValue = "true", label = "consumer,scheduler",
062            description = "Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in JDK for details.")
063    private boolean useFixedDelay = true;
064    @UriParam(optionalPrefix = "consumer.", label = "consumer,advanced",
065            description = "A pluggable org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your custom implementation"
066                    + " to control error handling usually occurred during the poll operation before an Exchange have been created and being routed in Camel.")
067    private PollingConsumerPollStrategy pollStrategy = new DefaultPollingConsumerPollStrategy();
068    @UriParam(optionalPrefix = "consumer.", defaultValue = "TRACE", label = "consumer,scheduler",
069            description = "The consumer logs a start/complete log line when it polls. This option allows you to configure the logging level for that.")
070    private LoggingLevel runLoggingLevel = LoggingLevel.TRACE;
071    @UriParam(optionalPrefix = "consumer.", label = "consumer",
072            description = "If the polling consumer did not poll any files, you can enable this option to send an empty message (no body) instead.")
073    private boolean sendEmptyMessageWhenIdle;
074    @UriParam(optionalPrefix = "consumer.", label = "consumer,scheduler",
075            description = "If greedy is enabled, then the ScheduledPollConsumer will run immediately again, if the previous run polled 1 or more messages.")
076    private boolean greedy;
077    @UriParam(optionalPrefix = "consumer.", enums = "none,spring,quartz2",
078            defaultValue = "none", label = "consumer,scheduler", description = "To use a cron scheduler from either camel-spring or camel-quartz2 component")
079    private ScheduledPollConsumerScheduler scheduler;
080    private String schedulerName = "none"; // used when configuring scheduler using a string value
081    @UriParam(prefix = "scheduler.", multiValue = true, label = "consumer,scheduler",
082            description = "To configure additional properties when using a custom scheduler or any of the Quartz2, Spring based scheduler.")
083    private Map<String, Object> schedulerProperties;
084    @UriParam(optionalPrefix = "consumer.", label = "consumer,scheduler",
085            description = "Allows for configuring a custom/shared thread pool to use for the consumer. By default each consumer has its own single threaded thread pool.")
086    private ScheduledExecutorService scheduledExecutorService;
087    @UriParam(optionalPrefix = "consumer.", label = "consumer,scheduler",
088            description = "To let the scheduled polling consumer backoff if there has been a number of subsequent idles/errors in a row."
089                    + " The multiplier is then the number of polls that will be skipped before the next actual attempt is happening again."
090                    + " When this option is in use then backoffIdleThreshold and/or backoffErrorThreshold must also be configured.")
091    private int backoffMultiplier;
092    @UriParam(optionalPrefix = "consumer.", label = "consumer,scheduler",
093            description = "The number of subsequent idle polls that should happen before the backoffMultipler should kick-in.")
094    private int backoffIdleThreshold;
095    @UriParam(optionalPrefix = "consumer.", label = "consumer,scheduler",
096            description = "The number of subsequent error polls (failed due some error) that should happen before the backoffMultipler should kick-in.")
097    private int backoffErrorThreshold;
098
099    protected ScheduledPollEndpoint(String endpointUri, Component component) {
100        super(endpointUri, component);
101    }
102
103    @Deprecated
104    protected ScheduledPollEndpoint(String endpointUri, CamelContext context) {
105        super(endpointUri, context);
106    }
107
108    @Deprecated
109    protected ScheduledPollEndpoint(String endpointUri) {
110        super(endpointUri);
111    }
112
113    protected ScheduledPollEndpoint() {
114    }
115
116    public void configureProperties(Map<String, Object> options) {
117        super.configureProperties(options);
118        configureScheduledPollConsumerProperties(options, getConsumerProperties());
119    }
120
121    protected void configureScheduledPollConsumerProperties(Map<String, Object> options, Map<String, Object> consumerProperties) {
122        // special for scheduled poll consumers as we want to allow end users to configure its options
123        // from the URI parameters without the consumer. prefix
124        Map<String, Object> schedulerProperties = IntrospectionSupport.extractProperties(options, "scheduler.");
125        if (schedulerProperties != null && !schedulerProperties.isEmpty()) {
126            setSchedulerProperties(schedulerProperties);
127        }
128
129        if (scheduler == null && schedulerName != null) {
130            if ("none".equals(schedulerName)) {
131                // no cron scheduler in use
132                scheduler = null;
133            } else if ("spring".equals(schedulerName)) {
134                // special for scheduler if its "spring" or "quartz2"
135                try {
136                    Class<? extends ScheduledPollConsumerScheduler> clazz = getCamelContext().getClassResolver().resolveMandatoryClass(SPRING_SCHEDULER, ScheduledPollConsumerScheduler.class);
137                    setScheduler(getCamelContext().getInjector().newInstance(clazz));
138                } catch (ClassNotFoundException e) {
139                    throw new IllegalArgumentException("Cannot load " + SPRING_SCHEDULER + " from classpath. Make sure camel-spring.jar is on the classpath.", e);
140                }
141            } else if ("quartz2".equals(schedulerName)) {
142                // special for scheduler if its "spring" or "quartz2"
143                try {
144                    Class<? extends ScheduledPollConsumerScheduler> clazz = getCamelContext().getClassResolver().resolveMandatoryClass(QUARTZ_2_SCHEDULER, ScheduledPollConsumerScheduler.class);
145                    setScheduler(getCamelContext().getInjector().newInstance(clazz));
146                } catch (ClassNotFoundException e) {
147                    throw new IllegalArgumentException("Cannot load " + QUARTZ_2_SCHEDULER + " from classpath. Make sure camel-quartz2.jar is on the classpath.", e);
148                }
149            } else {
150                // must refer to a custom scheduler by the given name
151                setScheduler(CamelContextHelper.mandatoryLookup(getCamelContext(), schedulerName, ScheduledPollConsumerScheduler.class));
152            }
153        }
154    }
155
156    @Override
157    protected void configurePollingConsumer(PollingConsumer consumer) throws Exception {
158        Map<String, Object> copy = new HashMap<String, Object>(getConsumerProperties());
159        Map<String, Object> throwaway = new HashMap<String, Object>();
160
161        // filter out unwanted options which is intended for the scheduled poll consumer
162        // as these options are not supported on the polling consumer
163        configureScheduledPollConsumerProperties(copy, throwaway);
164
165        // set reference properties first as they use # syntax that fools the regular properties setter
166        EndpointHelper.setReferenceProperties(getCamelContext(), consumer, copy);
167        EndpointHelper.setProperties(getCamelContext(), consumer, copy);
168
169        if (!isLenientProperties() && copy.size() > 0) {
170            throw new ResolveEndpointFailedException(this.getEndpointUri(), "There are " + copy.size()
171                    + " parameters that couldn't be set on the endpoint polling consumer."
172                    + " Check the uri if the parameters are spelt correctly and that they are properties of the endpoint."
173                    + " Unknown consumer parameters=[" + copy + "]");
174        }
175    }
176
177    protected void initConsumerProperties() {
178        // must setup consumer properties before we are ready to start
179        Map<String, Object> options = getConsumerProperties();
180        if (!options.containsKey("startScheduler")) {
181            options.put("startScheduler", isStartScheduler());
182        }
183        if (!options.containsKey("initialDelay")) {
184            options.put("initialDelay", getInitialDelay());
185        }
186        if (!options.containsKey("delay")) {
187            options.put("delay", getDelay());
188        }
189        if (!options.containsKey("timeUnit")) {
190            options.put("timeUnit", getTimeUnit());
191        }
192        if (!options.containsKey("useFixedDelay")) {
193            options.put("useFixedDelay", isUseFixedDelay());
194        }
195        if (!options.containsKey("pollStrategy")) {
196            options.put("pollStrategy", getPollStrategy());
197        }
198        if (!options.containsKey("runLoggingLevel")) {
199            options.put("runLoggingLevel", getRunLoggingLevel());
200        }
201        if (!options.containsKey("sendEmptyMessageWhenIdle")) {
202            options.put("sendEmptyMessageWhenIdle", isSendEmptyMessageWhenIdle());
203        }
204        if (!options.containsKey("greedy")) {
205            options.put("greedy", isGreedy());
206        }
207        if (!options.containsKey("scheduler")) {
208            options.put("scheduler", getScheduler());
209        }
210        if (!options.containsKey("schedulerProperties")) {
211            options.put("schedulerProperties", getSchedulerProperties());
212        }
213        if (!options.containsKey("scheduledExecutorService")) {
214            options.put("scheduledExecutorService", getScheduledExecutorService());
215        }
216        if (!options.containsKey("backoffMultiplier")) {
217            options.put("backoffMultiplier", getBackoffMultiplier());
218        }
219        if (!options.containsKey("backoffIdleThreshold")) {
220            options.put("backoffIdleThreshold", getBackoffIdleThreshold());
221        }
222        if (!options.containsKey("backoffErrorThreshold")) {
223            options.put("backoffErrorThreshold", getBackoffErrorThreshold());
224        }
225    }
226
227    @Override
228    protected void doStart() throws Exception {
229        initConsumerProperties();
230        super.doStart();
231    }
232
233    @Override
234    protected void doStop() throws Exception {
235        super.doStop();
236        // noop
237    }
238
239    public boolean isStartScheduler() {
240        return startScheduler;
241    }
242
243    /**
244     * Whether the scheduler should be auto started.
245     */
246    public void setStartScheduler(boolean startScheduler) {
247        this.startScheduler = startScheduler;
248    }
249
250    public long getInitialDelay() {
251        return initialDelay;
252    }
253
254    /**
255     * Milliseconds before the first poll starts.
256     * <p/>
257     * The default value is 1000.
258     * You can also specify time values using units, such as 60s (60 seconds), 5m30s (5 minutes and 30 seconds), and 1h (1 hour).
259     * @see <a href="http://camel.apache.org/how-do-i-specify-time-period-in-a-human-friendly-syntax.html">human friendly syntax</a>
260     */
261    public void setInitialDelay(long initialDelay) {
262        this.initialDelay = initialDelay;
263    }
264
265    public long getDelay() {
266        return delay;
267    }
268
269    /**
270     * Milliseconds before the next poll.
271     * <p/>
272     * The default value is 500.
273     * You can also specify time values using units, such as 60s (60 seconds), 5m30s (5 minutes and 30 seconds), and 1h (1 hour).
274     * @see <a href="http://camel.apache.org/how-do-i-specify-time-period-in-a-human-friendly-syntax.html">human friendly syntax</a>
275     */
276    public void setDelay(long delay) {
277        this.delay = delay;
278    }
279
280    public TimeUnit getTimeUnit() {
281        return timeUnit;
282    }
283
284    /**
285     * Time unit for initialDelay and delay options.
286     */
287    public void setTimeUnit(TimeUnit timeUnit) {
288        this.timeUnit = timeUnit;
289    }
290
291    public boolean isUseFixedDelay() {
292        return useFixedDelay;
293    }
294
295    /**
296     * Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in JDK for details.
297     */
298    public void setUseFixedDelay(boolean useFixedDelay) {
299        this.useFixedDelay = useFixedDelay;
300    }
301
302    public PollingConsumerPollStrategy getPollStrategy() {
303        return pollStrategy;
304    }
305
306    /**
307     * A pluggable org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your custom implementation
308     * to control error handling usually occurred during the poll operation before an Exchange have been created
309     * and being routed in Camel. In other words the error occurred while the polling was gathering information,
310     * for instance access to a file network failed so Camel cannot access it to scan for files.
311     * The default implementation will log the caused exception at WARN level and ignore it.
312     */
313    public void setPollStrategy(PollingConsumerPollStrategy pollStrategy) {
314        this.pollStrategy = pollStrategy;
315        // we are allowed to change poll strategy
316    }
317
318    public LoggingLevel getRunLoggingLevel() {
319        return runLoggingLevel;
320    }
321
322    /**
323     * The consumer logs a start/complete log line when it polls. This option allows you to configure the logging level for that.
324     */
325    public void setRunLoggingLevel(LoggingLevel runLoggingLevel) {
326        this.runLoggingLevel = runLoggingLevel;
327    }
328
329    public boolean isSendEmptyMessageWhenIdle() {
330        return sendEmptyMessageWhenIdle;
331    }
332
333    /**
334     * If the polling consumer did not poll any files, you can enable this option to send an empty message (no body) instead.
335     */
336    public void setSendEmptyMessageWhenIdle(boolean sendEmptyMessageWhenIdle) {
337        this.sendEmptyMessageWhenIdle = sendEmptyMessageWhenIdle;
338    }
339
340    public boolean isGreedy() {
341        return greedy;
342    }
343
344    /**
345     * If greedy is enabled, then the ScheduledPollConsumer will run immediately again, if the previous run polled 1 or more messages.
346     */
347    public void setGreedy(boolean greedy) {
348        this.greedy = greedy;
349    }
350
351    public ScheduledPollConsumerScheduler getScheduler() {
352        return scheduler;
353    }
354
355    /**
356     * Allow to plugin a custom org.apache.camel.spi.ScheduledPollConsumerScheduler to use as the scheduler for
357     * firing when the polling consumer runs. The default implementation uses the ScheduledExecutorService and
358     * there is a Quartz2, and Spring based which supports CRON expressions.
359     *
360     * Notice: If using a custom scheduler then the options for initialDelay, useFixedDelay, timeUnit,
361     * and scheduledExecutorService may not be in use. Use the text quartz2 to refer to use the Quartz2 scheduler;
362     * and use the text spring to use the Spring based; and use the text #myScheduler to refer to a custom scheduler
363     * by its id in the Registry. See Quartz2 page for an example.
364     */
365    public void setScheduler(ScheduledPollConsumerScheduler scheduler) {
366        this.scheduler = scheduler;
367    }
368
369    /**
370     * Allow to plugin a custom org.apache.camel.spi.ScheduledPollConsumerScheduler to use as the scheduler for
371     * firing when the polling consumer runs. This option is used for referring to one of the built-in schedulers
372     * either <tt>spring</tt>, or <tt>quartz2</tt>. Using <tt>none</tt> refers to no scheduler to be used.
373     */
374    public void setScheduler(String schedulerName) {
375        this.schedulerName = schedulerName;
376    }
377
378    public Map<String, Object> getSchedulerProperties() {
379        return schedulerProperties;
380    }
381
382    /**
383     * To configure additional properties when using a custom scheduler or any of the Quartz2, Spring based scheduler.
384     */
385    public void setSchedulerProperties(Map<String, Object> schedulerProperties) {
386        this.schedulerProperties = schedulerProperties;
387    }
388
389    public ScheduledExecutorService getScheduledExecutorService() {
390        return scheduledExecutorService;
391    }
392
393    /**
394     * Allows for configuring a custom/shared thread pool to use for the consumer.
395     * By default each consumer has its own single threaded thread pool.
396     * This option allows you to share a thread pool among multiple consumers.
397     */
398    public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
399        this.scheduledExecutorService = scheduledExecutorService;
400    }
401
402    public int getBackoffMultiplier() {
403        return backoffMultiplier;
404    }
405
406    /**
407     * To let the scheduled polling consumer backoff if there has been a number of subsequent idles/errors in a row.
408     * The multiplier is then the number of polls that will be skipped before the next actual attempt is happening again.
409     * When this option is in use then backoffIdleThreshold and/or backoffErrorThreshold must also be configured.
410     */
411    public void setBackoffMultiplier(int backoffMultiplier) {
412        this.backoffMultiplier = backoffMultiplier;
413    }
414
415    public int getBackoffIdleThreshold() {
416        return backoffIdleThreshold;
417    }
418
419    /**
420     * The number of subsequent idle polls that should happen before the backoffMultipler should kick-in.
421     */
422    public void setBackoffIdleThreshold(int backoffIdleThreshold) {
423        this.backoffIdleThreshold = backoffIdleThreshold;
424    }
425
426    public int getBackoffErrorThreshold() {
427        return backoffErrorThreshold;
428    }
429
430    /**
431     * The number of subsequent error polls (failed due some error) that should happen before the backoffMultipler should kick-in.
432     */
433    public void setBackoffErrorThreshold(int backoffErrorThreshold) {
434        this.backoffErrorThreshold = backoffErrorThreshold;
435    }
436
437}