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     */
017    package org.apache.camel.impl;
018    
019    import java.util.concurrent.ExecutorService;
020    import java.util.concurrent.ScheduledExecutorService;
021    import java.util.concurrent.ScheduledFuture;
022    import java.util.concurrent.TimeUnit;
023    
024    import org.apache.camel.Endpoint;
025    import org.apache.camel.Processor;
026    import org.apache.camel.SuspendableService;
027    import org.apache.camel.spi.PollingConsumerPollStrategy;
028    import org.apache.camel.util.ObjectHelper;
029    import org.apache.camel.util.concurrent.ExecutorServiceHelper;
030    import org.apache.commons.logging.Log;
031    import org.apache.commons.logging.LogFactory;
032    
033    /**
034     * A useful base class for any consumer which is polling based
035     * 
036     * @version $Revision: 896858 $
037     */
038    public abstract class ScheduledPollConsumer extends DefaultConsumer implements Runnable, SuspendableService {
039        private static final int DEFAULT_THREADPOOL_SIZE = 10;
040        private static final transient Log LOG = LogFactory.getLog(ScheduledPollConsumer.class);
041    
042        private final ScheduledExecutorService executor;
043        private ScheduledFuture<?> future;
044    
045        // if adding more options then align with ScheduledPollEndpoint#configureScheduledPollConsumerProperties
046        private long initialDelay = 1000;
047        private long delay = 500;
048        private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
049        private boolean useFixedDelay;
050        private PollingConsumerPollStrategy pollStrategy = new DefaultPollingConsumerPollStrategy();
051        private volatile boolean suspended;
052    
053        public ScheduledPollConsumer(DefaultEndpoint endpoint, Processor processor) {
054            super(endpoint, processor);
055    
056            ScheduledExecutorService scheduled;
057            ExecutorService service = endpoint.getExecutorService();
058            if (service instanceof ScheduledExecutorService) {
059                scheduled = (ScheduledExecutorService) service;
060            } else {
061                scheduled = ExecutorServiceHelper.newScheduledThreadPool(DEFAULT_THREADPOOL_SIZE, getEndpoint().getEndpointUri(), true);
062            }
063    
064            this.executor = scheduled;
065            ObjectHelper.notNull(executor, "executor");
066        }
067    
068        public ScheduledPollConsumer(Endpoint endpoint, Processor processor, ScheduledExecutorService executor) {
069            super(endpoint, processor);
070            this.executor = executor;
071            ObjectHelper.notNull(executor, "executor");
072        }
073    
074        /**
075         * Invoked whenever we should be polled
076         */
077        public void run() {
078            if (suspended) {
079                if (LOG.isTraceEnabled()) {
080                    LOG.trace("Cannot start to poll: " + this.getEndpoint() + " as its suspended");
081                }
082                return;
083            }
084    
085            int retryCounter = -1;
086            boolean done = false;
087    
088            while (!done) {
089                try {
090                    // eager assume we are done
091                    done = true;
092                    if (isPollAllowed()) {
093    
094                        if (retryCounter == -1) {
095                            if (LOG.isTraceEnabled()) {
096                                LOG.trace("Starting to poll: " + this.getEndpoint());
097                            }
098                        } else {
099                            if (LOG.isDebugEnabled()) {
100                                LOG.debug("Retrying attempt " + retryCounter + " to poll: " + this.getEndpoint());
101                            }
102                        }
103    
104                        pollStrategy.begin(this, getEndpoint());
105                        retryCounter++;
106                        poll();
107                        pollStrategy.commit(this, getEndpoint());
108                    }
109    
110                    if (LOG.isTraceEnabled()) {
111                        LOG.trace("Finished polling: " + this.getEndpoint());
112                    }
113                } catch (Exception e) {
114                    try {
115                        boolean retry = pollStrategy.rollback(this, getEndpoint(), retryCounter, e);
116                        if (retry) {
117                            done = false;
118                        }
119                    } catch (Exception re) {
120                        throw ObjectHelper.wrapRuntimeCamelException(re);
121                    }
122                } catch (Error e) {
123                    // log the fatal error as the JDK itself may not log it for us
124                    log.fatal("Consumer " + this +  " could not poll endpoint: " + getEndpoint().getEndpointUri() + " caused by: " + e.getMessage(), e);
125                    throw e;
126                }
127            }
128        }
129    
130        // Properties
131        // -------------------------------------------------------------------------
132    
133        protected boolean isPollAllowed() {
134            return isRunAllowed() && !isSuspended();
135        }
136    
137        public long getInitialDelay() {
138            return initialDelay;
139        }
140    
141        public void setInitialDelay(long initialDelay) {
142            this.initialDelay = initialDelay;
143        }
144    
145        public long getDelay() {
146            return delay;
147        }
148    
149        public void setDelay(long delay) {
150            this.delay = delay;
151        }
152    
153        public TimeUnit getTimeUnit() {
154            return timeUnit;
155        }
156    
157        public void setTimeUnit(TimeUnit timeUnit) {
158            this.timeUnit = timeUnit;
159        }
160    
161        public boolean isUseFixedDelay() {
162            return useFixedDelay;
163        }
164    
165        public void setUseFixedDelay(boolean useFixedDelay) {
166            this.useFixedDelay = useFixedDelay;
167        }
168    
169        public PollingConsumerPollStrategy getPollStrategy() {
170            return pollStrategy;
171        }
172    
173        public void setPollStrategy(PollingConsumerPollStrategy pollStrategy) {
174            this.pollStrategy = pollStrategy;
175        }
176    
177        public void suspend() {
178            suspended = true;
179        }
180    
181        public void resume() {
182            suspended = false;
183        }
184    
185        public boolean isSuspended() {
186            return suspended;
187        }
188    
189        // Implementation methods
190        // -------------------------------------------------------------------------
191    
192        /**
193         * The polling method which is invoked periodically to poll this consumer
194         * 
195         * @throws Exception can be thrown if an exception occurred during polling
196         */
197        protected abstract void poll() throws Exception;
198    
199        @Override
200        protected void doStart() throws Exception {
201            super.doStart();
202            if (isUseFixedDelay()) {
203                future = executor.scheduleWithFixedDelay(this, getInitialDelay(), getDelay(), getTimeUnit());
204            } else {
205                future = executor.scheduleAtFixedRate(this, getInitialDelay(), getDelay(), getTimeUnit());
206            }
207        }
208    
209        @Override
210        protected void doStop() throws Exception {
211            if (future != null) {
212                future.cancel(false);
213            }
214            super.doStop();
215        }
216    }