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: */
018:
019: /* $Id: SchedulerWrapper.java 473861 2006-11-12 03:51:14Z gregor $ */
020:
021: package org.apache.lenya.cms.scheduler;
022:
023: import java.io.File;
024: import java.text.DateFormat;
025: import java.text.SimpleDateFormat;
026: import java.util.ArrayList;
027: import java.util.Date;
028: import java.util.GregorianCalendar;
029: import java.util.List;
030:
031: import javax.servlet.http.HttpServletRequest;
032:
033: import org.apache.avalon.framework.configuration.Configuration;
034: import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
035: import org.apache.lenya.cms.publication.Publication;
036: import org.apache.lenya.cms.publication.PublicationException;
037: import org.apache.lenya.cms.scheduler.xml.TriggerHelper;
038: import org.apache.lenya.xml.NamespaceHelper;
039: import org.apache.log4j.Logger;
040: import org.quartz.JobDataMap;
041: import org.quartz.JobDetail;
042: import org.quartz.Scheduler;
043: import org.quartz.SchedulerException;
044: import org.quartz.SchedulerFactory;
045: import org.quartz.Trigger;
046: import org.quartz.impl.StdSchedulerFactory;
047: import org.w3c.dom.Document;
048: import org.w3c.dom.Element;
049:
050: /**
051: * Scheduler wrapper
052: */
053: public class SchedulerWrapper {
054:
055: private static Logger log = Logger
056: .getLogger(SchedulerWrapper.class);
057: /**
058: * <code>JOB_PREFIX</code> Job Namespace prefix
059: */
060: public static final String JOB_PREFIX = "job";
061: /**
062: * <code>JOB_ID</code> Job ID
063: */
064: public static final String JOB_ID = "id";
065: private static int jobId = 0;
066: private Scheduler scheduler = null;
067: private String servletContextPath;
068: private String schedulerConfigurationPath;
069: private SchedulerStore store = new SchedulerStore();
070:
071: /**
072: * Creates a new instance of SchedulerWrapper
073: *
074: * @param _servletContextPath The servlet context path.
075: * @param _schedulerConfigurationPath The scheduler configuration path.
076: */
077: public SchedulerWrapper(String _servletContextPath,
078: String _schedulerConfigurationPath) {
079: this .servletContextPath = _servletContextPath;
080: this .schedulerConfigurationPath = _schedulerConfigurationPath;
081:
082: SchedulerFactory factory = new StdSchedulerFactory();
083: log.info("------- Starting up -----------------------");
084:
085: try {
086: this .scheduler = factory.getScheduler();
087:
088: this .scheduler
089: .addSchedulerListener(new AbstractSchedulerListener());
090: this .scheduler.start();
091: } catch (SchedulerException e) {
092: log.error("Can't initialize SchedulerWrapper: ", e);
093: log.error("------- Startup failed -------------------");
094: }
095:
096: log.info("------- Startup complete ------------------");
097: }
098:
099: /**
100: * Returns the store.
101: * @return A scheduler store.
102: */
103: protected SchedulerStore getStore() {
104: return this .store;
105: }
106:
107: /**
108: * Returns the scheduler.
109: * @return A scheduler.
110: */
111: private Scheduler getScheduler() {
112: return this .scheduler;
113: }
114:
115: /**
116: * Shuts down the scheduler.
117: */
118: public void shutdown() {
119: log.info("------- Shutting Down ---------------------");
120:
121: // try to save state here
122: try {
123: getScheduler().shutdown();
124: } catch (SchedulerException e) {
125: log.error("------- Shutdown Failed -----------------", e);
126: }
127:
128: log.info("------- Shutdown Complete -----------------");
129: }
130:
131: /**
132: * Returns the servlet context path.
133: * @return The servlet context path.
134: */
135: protected String getServletContextPath() {
136: return this .servletContextPath;
137: }
138:
139: /**
140: * Returns the scheduler configuration path.
141: * @return A string.
142: */
143: protected String getSchedulerConfigurationPath() {
144: return this .schedulerConfigurationPath;
145: }
146:
147: /**
148: * Returns the next job ID to use (calculated using the current time).
149: * @return A string.
150: */
151: protected synchronized static String getNextJobId() {
152: return "job_" + jobId++ + System.currentTimeMillis();
153: }
154:
155: /**
156: * Adds a job.
157: * @param jobGroup The job group.
158: * @param startTime The start time.
159: * @param jobClass The class of the job.
160: * @param map The job parameters.
161: * @throws SchedulerException if an error occurs.
162: * @throws PublicationException if an error occurs.
163: */
164: protected void addJob(String jobGroup, Date startTime,
165: Class jobClass, JobDataMap map) throws SchedulerException,
166: PublicationException {
167: String uniqueJobId = getNextJobId();
168: log.debug("Job ID: [" + uniqueJobId + "]");
169:
170: JobDetail jobDetail = new JobDetail(uniqueJobId, jobGroup,
171: jobClass);
172: jobDetail.setJobDataMap(map);
173:
174: Date now = new GregorianCalendar().getTime();
175: if (log.isDebugEnabled()) {
176: DateFormat format = new SimpleDateFormat();
177: log.debug("Trigger time: [" + format.format(startTime)
178: + "]");
179: log.debug("Current time: [" + format.format(now) + "]");
180: }
181:
182: if (startTime.after(now)) {
183: Trigger trigger = TriggerHelper.createSimpleTrigger(
184: uniqueJobId, jobGroup, startTime);
185: addJob(jobDetail, trigger);
186: log.debug("Scheduling job.");
187: } else {
188: addJob(jobDetail);
189: log.debug("Adding job without scheduling.");
190: }
191:
192: log.debug("----------------------------------------------");
193:
194: this .store.writeSnapshot(getPublication(jobGroup),
195: getJobWrappers(jobGroup));
196: }
197:
198: /**
199: * Adds a job.
200: * @param jobGroup The job group.
201: * @param startTime The start time.
202: * @param request The request to obtain the parameters from.
203: * @throws SchedulerException when something went wrong.
204: */
205: public void addJob(String jobGroup, Date startTime,
206: HttpServletRequest request) throws SchedulerException {
207:
208: if (jobGroup == null) {
209: throw new SchedulerException("Job group must not be null!");
210: }
211:
212: try {
213: log.debug("----------------------------------------------");
214: log.debug("Adding Job for group [" + jobGroup + "]");
215:
216: // FIXME: more flexible
217: Class jobClass = TaskJob.class;
218:
219: ServletJob job = ServletJobFactory.createJob(jobClass);
220: JobDataMap map = job.createJobData(request);
221:
222: addJob(jobGroup, startTime, jobClass, map);
223: } catch (final SchedulerException e) {
224: log.error("Adding job failed: ", e);
225: throw new SchedulerException(e);
226: } catch (final PublicationException e) {
227: log.error("Adding job failed: ", e);
228: throw new SchedulerException(e);
229: }
230: }
231:
232: /**
233: * Returns the publication for a job group.
234: * @param jobGroup A job group.
235: * @return A publication.
236: * @throws PublicationException when the publication does not exist.
237: */
238: protected Publication getPublication(String jobGroup)
239: throws PublicationException {
240: return null;
241: /*
242: PublicationManagerImpl factory = PublicationManagerImpl.getInstance(new ConsoleLogger());
243: return factory.getPublication(jobGroup, getServletContextPath());
244: */
245: }
246:
247: /**
248: * Adds a job.
249: * @param detail The job information.
250: * @param trigger The trigger to trigger the job.
251: */
252: protected void addJob(JobDetail detail, Trigger trigger) {
253: try {
254: detail.setDurability(true);
255:
256: Date ft = getScheduler().scheduleJob(detail, trigger);
257: log.debug("Job " + detail.getFullName() + " will run at: "
258: + ft);
259: } catch (Exception e) {
260: log.error("Adding job failed: ", e);
261: }
262: }
263:
264: /**
265: * Adds a job.
266: * @param detail The job information.
267: */
268: protected void addJob(JobDetail detail) {
269: try {
270: detail.setDurability(true);
271: getScheduler().addJob(detail, true);
272: } catch (SchedulerException e) {
273: log.error("Adding job failed: ", e);
274: }
275: }
276:
277: /**
278: * Deletes a job.
279: * @param jobName The job name.
280: * @param jobGroup The job group.
281: */
282: protected void deleteJob(String jobName, String jobGroup) {
283: try {
284: log.debug("-----------------------------------");
285: log.debug("\n Deleting job [" + jobGroup + "/" + jobName
286: + "]");
287: log.debug("-----------------------------------");
288: getScheduler().deleteJob(jobName, jobGroup);
289: getStore().writeSnapshot(getPublication(jobGroup),
290: getJobWrappers(jobGroup));
291: } catch (SchedulerException e) {
292: log.error("Deleting job failed: ", e);
293: } catch (PublicationException e) {
294: log.error("Deleting job failed: ", e);
295: }
296:
297: }
298:
299: /**
300: * Reads the scheduler configuration.
301: * @return A configuration.
302: */
303: protected Configuration getSchedulerConfiguration() {
304: try {
305: DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
306: String path = getServletContextPath()
307: + getSchedulerConfigurationPath();
308: log.debug("Initializing scheduler configuration: " + path);
309:
310: File configurationFile = new File(path);
311: Configuration configuration = builder
312: .buildFromFile(configurationFile);
313:
314: return configuration;
315: } catch (Exception e) {
316: log.error("Can't initialize scheduler configuration: ", e);
317:
318: return null;
319: }
320: }
321:
322: /**
323: * <code>ELEMENT_TRIGGERS</code> Triggers element
324: */
325: public static final String ELEMENT_TRIGGERS = "triggers";
326: /**
327: * <code>ELEMENT_TRIGGER</code> Trigger element
328: */
329: public static final String ELEMENT_TRIGGER = "trigger";
330: /**
331: * <code>TYPE_ATTRIBUTE</code> Type attribute
332: */
333: public static final String TYPE_ATTRIBUTE = "type";
334: /**
335: * <code>CLASS_ATTRIBUTE</code> Class attribute
336: */
337: public static final String CLASS_ATTRIBUTE = "class";
338:
339: /**
340: * Returns an XML element containing the trigger types.
341: * @param helper The namespace helper of the document that shall contain the element.
342: * @return An XML element.
343: */
344: protected Element getTriggerTypes(NamespaceHelper helper) {
345: try {
346: Configuration configuration = getSchedulerConfiguration();
347: Configuration[] triggerConfigurations = configuration
348: .getChild(ELEMENT_TRIGGERS).getChildren(
349: ELEMENT_TRIGGER);
350:
351: Element triggersElement = helper.createElement("triggers");
352:
353: for (int i = 0; i < triggerConfigurations.length; i++) {
354: Configuration conf = triggerConfigurations[i];
355: String type = conf.getAttribute(TYPE_ATTRIBUTE);
356: String className = conf.getAttribute(CLASS_ATTRIBUTE);
357:
358: Element triggerElement = helper
359: .createElement("trigger");
360: triggerElement.setAttribute("name", type);
361: triggerElement.setAttribute("src", className);
362: triggersElement.appendChild(triggerElement);
363: }
364:
365: return triggersElement;
366: } catch (Exception e) {
367: log.error("Can't configure trigger types: " + e);
368:
369: return null;
370: }
371: }
372:
373: /**
374: * Returns the trigger of a certain job.
375: * @param jobName The job name.
376: * @param jobGroup The job group.
377: * @return A trigger.
378: * @throws SchedulerException when something went wrong.
379: */
380: protected Trigger getTrigger(String jobName, String jobGroup)
381: throws SchedulerException {
382: log.debug("Resolving trigger for job [" + jobName + " ][ "
383: + jobGroup + "]");
384: String[] triggerGroups = getScheduler().getTriggerGroupNames();
385:
386: for (int groupIndex = 0; groupIndex < triggerGroups.length; groupIndex++) {
387: String[] triggerNames = getScheduler().getTriggerNames(
388: triggerGroups[groupIndex]);
389:
390: for (int nameIndex = 0; nameIndex < triggerNames.length; nameIndex++) {
391: log.debug("Trigger name: " + triggerNames[nameIndex]);
392:
393: Trigger trigger = getScheduler().getTrigger(
394: triggerNames[nameIndex],
395: triggerGroups[groupIndex]);
396: log.debug("Job group: " + trigger.getJobGroup());
397:
398: if (trigger.getJobGroup().equals(jobGroup)
399: && trigger.getJobName().equals(jobName)) {
400: return trigger;
401: }
402: }
403: }
404:
405: return null;
406: }
407:
408: /**
409: * Return an XML description certain job groups.
410: * @param jobGroupNames The job group names.
411: * @return An XML document.
412: * @exception SchedulerException if an error occurs
413: */
414: public Document getSnapshot(String[] jobGroupNames)
415: throws SchedulerException {
416: log.debug("Creating job snapshot");
417:
418: NamespaceHelper helper = SchedulerStore.getNamespaceHelper();
419: Document document = helper.getDocument();
420: Element root = document.getDocumentElement();
421:
422: // print a list of all available trigger types
423: root.appendChild(getTriggerTypes(helper));
424:
425: for (int groupIndex = 0; groupIndex < jobGroupNames.length; groupIndex++) {
426: log.debug("Creating job snapshot for group ["
427: + jobGroupNames[groupIndex] + "]");
428: root.appendChild(getSnapshot(helper,
429: jobGroupNames[groupIndex]));
430: }
431:
432: return document;
433: }
434:
435: /**
436: * Returns the snapshot of a certain job group.
437: * @param helper The namespace helper.
438: * @param group The job group.
439: * @return An XML element.
440: * @throws SchedulerException when something went wrong.
441: */
442: protected Element getSnapshot(NamespaceHelper helper, String group)
443: throws SchedulerException {
444: JobWrapper[] jobs = getJobWrappers(group);
445: Element element;
446: try {
447: element = getStore().createSnapshot(helper,
448: getPublication(group), jobs);
449: } catch (SchedulerException e) {
450: throw e;
451: } catch (PublicationException e) {
452: throw new SchedulerException(e);
453: }
454: return element;
455: }
456:
457: /**
458: * Returns the job wrappers for a certain job group.
459: * @param jobGroupName The job group.
460: * @return An array of job wrappers.
461: * @throws SchedulerException when something went wrong.
462: */
463: protected JobWrapper[] getJobWrappers(String jobGroupName)
464: throws SchedulerException {
465:
466: List wrappers = new ArrayList();
467: String[] jobNames = getScheduler().getJobNames(jobGroupName);
468:
469: for (int nameIndex = 0; nameIndex < jobNames.length; nameIndex++) {
470: JobDetail jobDetail = getScheduler().getJobDetail(
471: jobNames[nameIndex], jobGroupName);
472: Trigger trigger = getTrigger(jobNames[nameIndex],
473: jobGroupName);
474: wrappers.add(new JobWrapper(jobDetail, trigger));
475: }
476:
477: return (JobWrapper[]) wrappers.toArray(new JobWrapper[wrappers
478: .size()]);
479: }
480:
481: /**
482: * Return an xml description of all scheduled jobs.
483: * @return The description
484: * @exception SchedulerException if an error occurs
485: */
486: public Document getSnapshot() throws SchedulerException {
487: String[] jobGroupNames = getScheduler().getJobGroupNames();
488: return getSnapshot(jobGroupNames);
489: }
490:
491: /**
492: * Restores the jobs of a certain job group from the snapshot file.
493: * @param jobGroup The job group.
494: * @throws SchedulerException when something went wrong.
495: */
496: public void restoreJobs(String jobGroup) throws SchedulerException {
497:
498: log.debug("--------------------------------------------------");
499: log.debug("Restoring jobs for job group [" + jobGroup + "]");
500: log.debug("--------------------------------------------------");
501:
502: try {
503: JobWrapper[] jobs = getStore().restoreJobs(
504: getPublication(jobGroup));
505: for (int i = 0; i < jobs.length; i++) {
506: if (jobs[i].getTrigger() != null) {
507: if (log.isDebugEnabled()) {
508: log
509: .debug(" Trigger time in future - scheduling job.");
510: }
511: addJob(jobs[i].getJobDetail(), jobs[i].getTrigger());
512: } else {
513: if (log.isDebugEnabled()) {
514: log
515: .debug(" Trigger time has expired - adding job without scheduling.");
516: }
517: addJob(jobs[i].getJobDetail());
518: }
519: }
520: } catch (final SchedulerException e) {
521: log.error("" + e.toString());
522: throw new SchedulerException(e);
523: } catch (final PublicationException e) {
524: log.error("" + e.toString());
525: throw new SchedulerException(e);
526: }
527:
528: }
529:
530: /**
531: * Modifies the execution time of a job.
532: * @param _jobId The job ID.
533: * @param jobGroup The job group.
534: * @param startTime The new start time.
535: * @throws SchedulerException when the job was not found.
536: */
537: public void modifyJob(String _jobId, String jobGroup, Date startTime)
538: throws SchedulerException {
539: log.debug("Modifying job [" + _jobId + "][" + jobGroup + "]");
540:
541: JobDetail jobDetail = getScheduler().getJobDetail(_jobId,
542: jobGroup);
543: if (jobDetail == null) {
544: throw new SchedulerException("Job not found!");
545: }
546:
547: Trigger trigger = getTrigger(jobDetail.getName(), jobGroup);
548: if (trigger == null) {
549: log.debug(" No trigger found.");
550: } else {
551: log.debug(" Trigger found. Setting new start time.");
552: jobDetail.setDurability(true);
553: if (startTime.after(new GregorianCalendar().getTime())) {
554: log
555: .debug(" Start time is in future - re-scheduling job.");
556: getScheduler().unscheduleJob(trigger.getName(),
557: trigger.getGroup());
558: trigger = TriggerHelper.createSimpleTrigger(_jobId,
559: jobGroup, startTime);
560: getScheduler().scheduleJob(trigger);
561: } else {
562: log
563: .debug(" Start time has already expired - deleting job.");
564: getScheduler().deleteJob(_jobId, jobGroup);
565: }
566: try {
567: getStore().writeSnapshot(getPublication(jobGroup),
568: getJobWrappers(jobGroup));
569: } catch (SchedulerException e) {
570: throw e;
571: } catch (PublicationException e) {
572: throw new SchedulerException(e);
573: }
574: }
575: }
576:
577: /**
578: * Deletes the jobs for a certain document. This method is called when
579: * a document has been moved or deleted.
580: * @param document A document.
581: * @throws SchedulerException when something went wrong.
582: * @throws PublicationException when something went wrong.
583: */
584: public void deleteJobs(
585: org.apache.lenya.cms.publication.Document document)
586: throws SchedulerException, PublicationException {
587:
588: log.debug("Deleting jobs for document [" + document + "]");
589:
590: String jobGroup = document.getPublication().getId();
591: JobWrapper[] jobs = getJobWrappers(jobGroup);
592: boolean changed = false;
593: for (int i = 0; i < jobs.length; i++) {
594: ServletJob job = jobs[i].getJob();
595: String documentUrl = job.getDocumentUrl(jobs[i]
596: .getJobDetail());
597: if (documentUrl.equals(document.getCanonicalWebappURL())) {
598: deleteJob(jobs[i].getJobDetail().getName(), jobGroup);
599: changed = true;
600: }
601: }
602: if (changed) {
603: getStore().writeSnapshot(getPublication(jobGroup),
604: getJobWrappers(jobGroup));
605: }
606: }
607:
608: }
|