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