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 }