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.component.quartz;
018    
019    import java.io.IOException;
020    import java.io.InputStream;
021    import java.net.URI;
022    import java.text.ParseException;
023    import java.util.Date;
024    import java.util.Map;
025    import java.util.Properties;
026    import java.util.concurrent.atomic.AtomicInteger;
027    
028    import org.apache.camel.CamelContext;
029    import org.apache.camel.StartupListener;
030    import org.apache.camel.impl.DefaultComponent;
031    import org.apache.camel.util.IntrospectionSupport;
032    import org.apache.camel.util.ObjectHelper;
033    import org.apache.commons.logging.Log;
034    import org.apache.commons.logging.LogFactory;
035    import org.quartz.CronTrigger;
036    import org.quartz.JobDetail;
037    import org.quartz.Scheduler;
038    import org.quartz.SchedulerException;
039    import org.quartz.SchedulerFactory;
040    import org.quartz.SimpleTrigger;
041    import org.quartz.Trigger;
042    import org.quartz.impl.StdSchedulerFactory;
043    
044    /**
045     * A <a href="http://camel.apache.org/quartz.html">Quartz Component</a>
046     * <p/>
047     * For a brief tutorial on setting cron expression see
048     * <a href="http://www.opensymphony.com/quartz/wikidocs/CronTriggers%20Tutorial.html">Quartz cron tutorial</a>.
049     *
050     * @version $Revision:520964 $
051     */
052    public class QuartzComponent extends DefaultComponent implements StartupListener {
053        private static final transient Log LOG = LogFactory.getLog(QuartzComponent.class);
054        private static final AtomicInteger JOBS = new AtomicInteger();
055        private static Scheduler scheduler;
056        private SchedulerFactory factory;
057        private Properties properties;
058        private String propertiesFile;
059        private int startDelayedSeconds;
060        private boolean autoStartScheduler = true;
061    
062        public QuartzComponent() {
063        }
064    
065        public QuartzComponent(final CamelContext context) {
066            super(context);
067        }
068    
069        @Override
070        protected QuartzEndpoint createEndpoint(final String uri, final String remaining, final Map<String, Object> parameters) throws Exception {
071            QuartzEndpoint answer = new QuartzEndpoint(uri, this);
072    
073            // lets split the remaining into a group/name
074            URI u = new URI(uri);
075            String path = ObjectHelper.after(u.getPath(), "/");
076            String host = u.getHost();
077            String cron = getAndRemoveParameter(parameters, "cron", String.class);
078            Boolean fireNow = getAndRemoveParameter(parameters, "fireNow", Boolean.class, Boolean.FALSE);
079    
080            // group can be optional, if so set it to Camel
081            String name;
082            String group;
083            if (ObjectHelper.isNotEmpty(path) && ObjectHelper.isNotEmpty(host)) {
084                group = host;
085                name = path;
086            } else {
087                group = "Camel";
088                name = host;
089            }
090    
091            Map<String, Object> triggerParameters = IntrospectionSupport.extractProperties(parameters, "trigger.");
092            Map<String, Object> jobParameters = IntrospectionSupport.extractProperties(parameters, "job.");
093    
094            // create the trigger either cron or simple
095            Trigger trigger;
096            if (ObjectHelper.isNotEmpty(cron)) {
097                trigger = createCronTrigger(cron);
098            } else {
099                trigger = new SimpleTrigger();
100                if (fireNow) {
101                    String intervalString = (String) triggerParameters.get("repeatInterval");
102                    if (intervalString != null) {
103                        long interval = Long.valueOf(intervalString);
104                        trigger.setStartTime(new Date(System.currentTimeMillis() - interval));
105                    }
106                }
107            }
108            answer.setTrigger(trigger);
109    
110            trigger.setName(name);
111            trigger.setGroup(group);
112    
113            setProperties(trigger, triggerParameters);
114            setProperties(answer.getJobDetail(), jobParameters);
115    
116            return answer;
117        }
118    
119        protected CronTrigger createCronTrigger(String path) throws ParseException {
120            // replace + back to space so its a cron expression
121            path = path.replaceAll("\\+", " ");
122            CronTrigger cron = new CronTrigger();
123            cron.setCronExpression(path);
124            return cron;
125        }
126    
127        public void onCamelContextStarted(CamelContext camelContext, boolean alreadyStarted) throws Exception {
128            // if not configure to auto start then don't start it
129            if (!isAutoStartScheduler()) {
130                LOG.info("QuartzComponent configured to not auto start Quartz scheduler.");
131                return;
132            }
133    
134            // only start scheduler when CamelContext have finished starting
135            startScheduler();
136        }
137    
138        @Override
139        protected void doStart() throws Exception {
140            super.doStart();
141            if (scheduler == null) {
142                scheduler = getScheduler();
143            }
144        }
145    
146        @Override
147        protected void doStop() throws Exception {
148            super.doStop();
149    
150            if (scheduler != null) {
151                int number = JOBS.get();
152                if (number > 0) {
153                    LOG.info("Cannot shutdown Quartz scheduler: " + scheduler.getSchedulerName() + " as there are still " + number + " jobs registered.");
154                } else {
155                    // no more jobs then shutdown the scheduler
156                    LOG.info("There are no more jobs registered, so shutting down Quartz scheduler: " + scheduler.getSchedulerName());
157                    scheduler.shutdown();
158                    scheduler = null;
159                }
160            }
161        }
162    
163        public void addJob(JobDetail job, Trigger trigger) throws SchedulerException {
164            JOBS.incrementAndGet();
165    
166            if (getScheduler().getTrigger(trigger.getName(), trigger.getGroup()) == null) {
167                if (LOG.isDebugEnabled()) {
168                    LOG.debug("Adding job using trigger: " + trigger.getGroup() + "/" + trigger.getName());
169                }
170                getScheduler().scheduleJob(job, trigger);
171            } else {
172                if (LOG.isDebugEnabled()) {
173                    LOG.debug("Resuming job using trigger: " + trigger.getGroup() + "/" + trigger.getName());
174                }
175                getScheduler().resumeTrigger(trigger.getName(), trigger.getGroup());
176            }
177        }
178    
179        public void removeJob(JobDetail job, Trigger trigger) throws SchedulerException {
180            JOBS.decrementAndGet();
181    
182            if (isClustered()) {
183                // do not remove jobs which are clustered, as we want the jobs to continue running on the other nodes
184                if (LOG.isDebugEnabled()) {
185                    LOG.debug("Cannot removing job using trigger: " + trigger.getGroup() + "/" + trigger.getName() + " as the JobStore is clustered.");
186                }
187                return;
188            }
189    
190            // only unschedule volatile jobs
191            if (job.isVolatile()) {
192                if (LOG.isDebugEnabled()) {
193                    LOG.debug("Removing job using trigger: " + trigger.getGroup() + "/" + trigger.getName());
194                }
195                getScheduler().unscheduleJob(trigger.getName(), trigger.getGroup());
196            } else {
197                // but pause jobs so we can resume them if the application restarts
198                if (LOG.isDebugEnabled()) {
199                    LOG.debug("Pausing job using trigger: " + trigger.getGroup() + "/" + trigger.getName());
200                }
201                getScheduler().pauseTrigger(trigger.getName(), trigger.getGroup());
202            }
203        }
204    
205        /**
206         * To force shutdown the quartz scheduler
207         *
208         * @throws SchedulerException can be thrown if error shutting down
209         */
210        public void shutdownScheduler() throws SchedulerException {
211            if (scheduler != null) {
212                LOG.info("Forcing shutdown of Quartz scheduler: " + scheduler.getSchedulerName());
213                scheduler.shutdown();
214                scheduler = null;
215            }
216        }
217    
218        /**
219         * Is the quartz scheduler clustered?
220         */
221        public boolean isClustered() throws SchedulerException {
222            return getScheduler().getMetaData().isJobStoreClustered();
223        }
224    
225        /**
226         * To force starting the quartz scheduler
227         *
228         * @throws SchedulerException can be thrown if error starting
229         */
230        public void startScheduler() throws SchedulerException {
231            if (scheduler != null && !scheduler.isStarted()) {
232                if (getStartDelayedSeconds() > 0) {
233                    LOG.info("Starting Quartz scheduler: " + scheduler.getSchedulerName() + " delayed: " + getStartDelayedSeconds() + " seconds.");
234                    scheduler.startDelayed(getStartDelayedSeconds());
235                } else {
236                    LOG.info("Starting Quartz scheduler: " + scheduler.getSchedulerName());
237                    scheduler.start();
238                }
239            }
240        }
241    
242        // Properties
243        // -------------------------------------------------------------------------
244    
245        public SchedulerFactory getFactory() throws SchedulerException {
246            if (factory == null) {
247                factory = createSchedulerFactory();
248            }
249            return factory;
250        }
251    
252        public void setFactory(final SchedulerFactory factory) {
253            this.factory = factory;
254        }
255    
256        public synchronized Scheduler getScheduler() throws SchedulerException {
257            if (scheduler == null) {
258                scheduler = createScheduler();
259            }
260            return scheduler;
261        }
262    
263        public void setScheduler(final Scheduler scheduler) {
264            QuartzComponent.scheduler = scheduler;
265        }
266    
267        public Properties getProperties() {
268            return properties;
269        }
270    
271        public void setProperties(Properties properties) {
272            this.properties = properties;
273        }
274    
275        public String getPropertiesFile() {
276            return propertiesFile;
277        }
278    
279        public void setPropertiesFile(String propertiesFile) {
280            this.propertiesFile = propertiesFile;
281        }
282    
283        public int getStartDelayedSeconds() {
284            return startDelayedSeconds;
285        }
286    
287        public void setStartDelayedSeconds(int startDelayedSeconds) {
288            this.startDelayedSeconds = startDelayedSeconds;
289        }
290    
291        public boolean isAutoStartScheduler() {
292            return autoStartScheduler;
293        }
294    
295        public void setAutoStartScheduler(boolean autoStartScheduler) {
296            this.autoStartScheduler = autoStartScheduler;
297        }
298    
299        // Implementation methods
300        // -------------------------------------------------------------------------
301    
302        protected Properties loadProperties() throws SchedulerException {
303            Properties answer = getProperties();
304            if (answer == null && getPropertiesFile() != null) {
305                if (LOG.isInfoEnabled()) {
306                    LOG.info("Loading Quartz properties file from classpath: " + getPropertiesFile());
307                }
308                InputStream is = getCamelContext().getClassResolver().loadResourceAsStream(getPropertiesFile());
309                if (is == null) {
310                    throw new SchedulerException("Quartz properties file not found in classpath: " + getPropertiesFile());
311                }
312                answer = new Properties();
313                try {
314                    answer.load(is);
315                } catch (IOException e) {
316                    throw new SchedulerException("Error loading Quartz properties file from classpath: " + getPropertiesFile(), e);
317                }
318            }
319            return answer;
320        }
321    
322        protected SchedulerFactory createSchedulerFactory() throws SchedulerException {
323            Properties prop = loadProperties();
324            if (prop != null) {
325                if (LOG.isDebugEnabled()) {
326                    LOG.debug("Creating SchedulerFactory with properties: " + prop);
327                }
328                return new StdSchedulerFactory(prop);
329            } else {
330                return new StdSchedulerFactory();
331            }
332        }
333    
334        protected Scheduler createScheduler() throws SchedulerException {
335            Scheduler scheduler = getFactory().getScheduler();
336            scheduler.getContext().put(QuartzConstants.QUARTZ_CAMEL_CONTEXT, getCamelContext());
337            return scheduler;
338        }
339    }