All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.quartz.xml.JobSchedulingDataProcessor Maven / Gradle / Ivy

Go to download

SDK for dev_appserver (local development) with some of the dependencies shaded (repackaged)

There is a newer version: 2.0.31
Show newest version
/* 
 * Copyright 2004-2005 OpenSymphony 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
 * use this file except in compliance with the License. You may obtain a copy 
 * of the License at 
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *   
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 * License for the specific language governing permissions and limitations 
 * under the License.
 * 
 */

/*
 * Previously Copyright (c) 2001-2004 James House
 */
package org.quartz.xml;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import javax.xml.parsers.ParserConfigurationException;

import com.google.appengine.repackaged.org.apache.commons.beanutils.ConversionException;
import com.google.appengine.repackaged.org.apache.commons.beanutils.Converter;
import com.google.appengine.repackaged.org.apache.commons.beanutils.DynaBean;
import com.google.appengine.repackaged.org.apache.commons.beanutils.DynaProperty;
import com.google.appengine.repackaged.org.apache.commons.beanutils.PropertyUtils;
import com.google.appengine.repackaged.org.apache.commons.digester.BeanPropertySetterRule;
import com.google.appengine.repackaged.org.apache.commons.digester.Digester;
import com.google.appengine.repackaged.org.apache.commons.digester.RuleSetBase;
import com.google.appengine.repackaged.org.apache.commons.logging.Log;
import com.google.appengine.repackaged.org.apache.commons.logging.LogFactory;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobListener;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;


/**
 * Parses an XML file that declares Jobs and their schedules (Triggers).
 * 
 * The xml document must conform to the format defined in
 * "job_scheduling_data_1_2.dtd" or "job_scheduling_data_1_2.xsd"
 * 
 * After creating an instance of this class, you should call one of the processFile()
 * functions, after which you may call the getScheduledJobs()
 * function to get a handle to the defined Jobs and Triggers, which can then be
 * scheduled with the Scheduler. Alternatively, you could call
 * the processFileAndScheduleJobs() function to do all of this
 * in one step.
 * 
 * The same instance can be used again and again, with the list of defined Jobs
 * being cleared each time you call a processFile method,
 * however a single instance is not thread-safe.
 * 
 * @author Chris Bonham
 * @author James House
 */
public class JobSchedulingDataProcessor extends DefaultHandler {
    /*
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     * 
     * Constants.
     * 
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */

    public static final String QUARTZ_PUBLIC_ID = "-//Quartz Enterprise Job Scheduler//DTD Job Scheduling Data 1.5//EN";

    public static final String QUARTZ_SYSTEM_ID = "http://www.opensymphony.com/quartz/xml/job_scheduling_data_1_5.dtd";
    
    public static final String QUARTZ_DTD = "/org/quartz/xml/job_scheduling_data_1_5.dtd";
    
    public static final String QUARTZ_NS = "http://www.opensymphony.com/quartz/JobSchedulingData";
    
    public static final String QUARTZ_SCHEMA = "http://www.opensymphony.com/quartz/xml/job_scheduling_data_1_5.xsd";
    
    public static final String QUARTZ_XSD = "/org/quartz/xml/job_scheduling_data_1_5.xsd";

    public static final String QUARTZ_SYSTEM_ID_DIR_PROP = "quartz.system.id.dir";

    public static final String QUARTZ_XML_FILE_NAME = "quartz_jobs.xml";

    public static final String QUARTZ_SYSTEM_ID_PREFIX = "jar:";

    protected static final String TAG_QUARTZ = "quartz";
    
    protected static final String TAG_OVERWRITE_EXISTING_JOBS = "overwrite-existing-jobs";

    protected static final String TAG_JOB_LISTENER = "job-listener";
    
    protected static final String TAG_CALENDAR = "calendar";
    
    protected static final String TAG_CLASS_NAME = "class-name";
    
    protected static final String TAG_DESCRIPTION = "description";

    protected static final String TAG_BASE_CALENDAR = "base-calendar";
    
    protected static final String TAG_MISFIRE_INSTRUCTION = "misfire-instruction";
    
    protected static final String TAG_CALENDAR_NAME = "calendar-name";

    protected static final String TAG_JOB = "job";

    protected static final String TAG_JOB_DETAIL = "job-detail";

    protected static final String TAG_NAME = "name";

    protected static final String TAG_GROUP = "group";

    protected static final String TAG_JOB_CLASS = "job-class";

    protected static final String TAG_JOB_LISTENER_REF = "job-listener-ref";
    
    protected static final String TAG_VOLATILITY = "volatility";

    protected static final String TAG_DURABILITY = "durability";

    protected static final String TAG_RECOVER = "recover";
    
    protected static final String TAG_JOB_DATA_MAP = "job-data-map";
    
    protected static final String TAG_ENTRY = "entry";
    
    protected static final String TAG_KEY = "key";
    
    protected static final String TAG_ALLOWS_TRANSIENT_DATA = "allows-transient-data";
    
    protected static final String TAG_VALUE = "value";

    protected static final String TAG_TRIGGER = "trigger";

    protected static final String TAG_SIMPLE = "simple";

    protected static final String TAG_CRON = "cron";

    protected static final String TAG_JOB_NAME = "job-name";

    protected static final String TAG_JOB_GROUP = "job-group";

    protected static final String TAG_START_TIME = "start-time";

    protected static final String TAG_END_TIME = "end-time";

    protected static final String TAG_REPEAT_COUNT = "repeat-count";

    protected static final String TAG_REPEAT_INTERVAL = "repeat-interval";

    protected static final String TAG_CRON_EXPRESSION = "cron-expression";

    protected static final String TAG_TIME_ZONE = "time-zone";

    /**
     * XML Schema dateTime datatype format.
     * 

* See * http://www.w3.org/TR/2001/REC-xmlschema-2-20010502/#dateTime */ protected static final String XSD_DATE_FORMAT = "yyyy-MM-dd'T'hh:mm:ss"; /** * Legacy DTD version 1.0 date format. */ protected static final String DTD_DATE_FORMAT = "yyyy-MM-dd hh:mm:ss a"; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ protected Map scheduledJobs = new HashMap(); protected List jobsToSchedule = new LinkedList(); protected List calsToSchedule = new LinkedList(); protected List listenersToSchedule = new LinkedList(); protected Collection validationExceptions = new ArrayList(); protected Digester digester; private boolean overWriteExistingJobs = true; private ThreadLocal schedLocal = new ThreadLocal(); /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Constructor for QuartzMetaDataProcessor. */ public JobSchedulingDataProcessor() { this(true, true, true); } /** * Constructor for QuartzMetaDataProcessor. * * @param useContextClassLoader whether or not to use the context class loader. * @param validating whether or not to validate XML. * @param validatingSchema whether or not to validate XML schema. */ public JobSchedulingDataProcessor(boolean useContextClassLoader, boolean validating, boolean validatingSchema) { initDigester(useContextClassLoader, validating, validatingSchema); } /** * Initializes the digester. * * @param useContextClassLoader whether or not to use the context class loader. * @param validating whether or not to validate XML. * @param validatingSchema whether or not to validate XML schema. */ protected void initDigester(boolean useContextClassLoader, boolean validating, boolean validatingSchema) { digester = new Digester(); digester.setNamespaceAware(true); digester.setUseContextClassLoader(useContextClassLoader); digester.setValidating(validating); initSchemaValidation(validatingSchema); digester.setEntityResolver(this); digester.setErrorHandler(this); if(addCustomDigesterRules(digester)) addDefaultDigesterRules(digester); } /** * Add the default set of digest rules */ protected void addDefaultDigesterRules(Digester digester) { digester.addSetProperties(TAG_QUARTZ, TAG_OVERWRITE_EXISTING_JOBS, "overWriteExistingJobs"); digester.addObjectCreate(TAG_QUARTZ + "/" + TAG_JOB_LISTENER, "jobListener","class-name"); digester.addCallMethod(TAG_QUARTZ + "/" + TAG_JOB_LISTENER,"setName",1); digester.addCallParam(TAG_QUARTZ + "/" + TAG_JOB_LISTENER,0,"name"); digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB_LISTENER,"addListenerToSchedule"); digester.addRuleSet(new CalendarRuleSet(TAG_QUARTZ + "/" + TAG_CALENDAR, "addCalendarToSchedule")); digester.addRuleSet(new CalendarRuleSet("*/" + TAG_BASE_CALENDAR, "setBaseCalendar")); digester.addObjectCreate(TAG_QUARTZ + "/" + TAG_JOB, JobSchedulingBundle.class); digester.addObjectCreate(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL, JobDetail.class); digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_NAME, "name"); digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_GROUP, "group"); digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_DESCRIPTION, "description"); digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_CLASS, "jobClass"); digester.addCallMethod(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_LISTENER_REF,"addJobListener",0 ); digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_VOLATILITY, "volatility"); digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_DURABILITY, "durability"); digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_RECOVER, "requestsRecovery"); digester.addObjectCreate(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_DATA_MAP, JobDataMap.class); digester.addSetProperties(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_DATA_MAP, TAG_ALLOWS_TRANSIENT_DATA, "allowsTransientData"); digester.addCallMethod(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_DATA_MAP + "/" + TAG_ENTRY, "put", 2, new Class[] { Object.class, Object.class }); digester.addCallParam(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_DATA_MAP + "/" + TAG_ENTRY + "/" + TAG_KEY, 0); digester.addCallParam(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_DATA_MAP + "/" + TAG_ENTRY + "/" + TAG_VALUE, 1); digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL + "/" + TAG_JOB_DATA_MAP, "setJobDataMap"); digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_JOB_DETAIL, "setJobDetail"); digester.addRuleSet(new TriggerRuleSet(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_SIMPLE, SimpleTrigger.class)); digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_SIMPLE + "/" + TAG_REPEAT_COUNT, "repeatCount"); digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_SIMPLE + "/" + TAG_REPEAT_INTERVAL, "repeatInterval"); digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_SIMPLE, "addTrigger"); digester.addRuleSet(new TriggerRuleSet(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_CRON, CronTrigger.class)); digester.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_CRON + "/" + TAG_CRON_EXPRESSION, "cronExpression"); digester.addRule(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_CRON + "/" + TAG_TIME_ZONE, new SimpleConverterRule("timeZone", new TimeZoneConverter(), TimeZone.class)); digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_CRON, "addTrigger"); digester.addSetNext(TAG_QUARTZ + "/" + TAG_JOB, "addJobToSchedule"); } /** * Template method provided as a hook for those who wish to extend this * class and add more functionality. * * This method is invoked after the Digester is instantiated, and before * the default set of rules are added. * * @param digester * @return false, if the default rules should NOT be added */ protected boolean addCustomDigesterRules(Digester digester) { // do nothing in base impl return true; } /** * Initializes the digester for XML Schema validation. * * @param validating whether or not to validate XML. */ protected void initSchemaValidation(boolean validatingSchema) { if (validatingSchema) { String schemaUri = null; URL url = getClass().getResource(QUARTZ_XSD); if (url != null) { schemaUri = url.toExternalForm(); } else { schemaUri = QUARTZ_SCHEMA; } digester.setSchema(schemaUri); } } protected static Log getLog() { return LogFactory.getLog(JobSchedulingDataProcessor.class); } /** * Returns whether to use the context class loader. * * @return whether to use the context class loader. */ public boolean getUseContextClassLoader() { return digester.getUseContextClassLoader(); } /** * Sets whether to use the context class loader. * * @param useContextClassLoader boolean. */ public void setUseContextClassLoader(boolean useContextClassLoader) { digester.setUseContextClassLoader(useContextClassLoader); } /** * Returns whether to overwrite existing jobs. * * @return whether to overwrite existing jobs. */ public boolean getOverWriteExistingJobs() { return overWriteExistingJobs; } /** * Sets whether to overwrite existing jobs. * * @param overWriteExistingJobs boolean. */ public void setOverWriteExistingJobs(boolean overWriteExistingJobs) { this.overWriteExistingJobs = overWriteExistingJobs; } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Process the xml file in the default location (a file named * "quartz_jobs.xml" in the current working directory). * */ public void processFile() throws Exception { processFile(QUARTZ_XML_FILE_NAME); } /** * Process the xml file named fileName. * * @param fileName * meta data file name. */ public void processFile(String fileName) throws Exception { processFile(fileName, fileName); } /** * Process the xmlfile named fileName with the given system * ID. * * @param fileName * meta data file name. * @param systemId * system ID. */ public void processFile(String fileName, String systemId) throws ValidationException, ParserConfigurationException, SAXException, IOException, SchedulerException, ClassNotFoundException, ParseException { clearValidationExceptions(); scheduledJobs.clear(); jobsToSchedule.clear(); calsToSchedule.clear(); getLog().info("Parsing XML file: " + fileName + " with systemId: " + systemId + " validating: " + digester.getValidating() + " validating schema: " + digester.getSchema()); InputSource is = new InputSource(getInputStream(fileName)); is.setSystemId(systemId); digester.push(this); digester.parse(is); maybeThrowValidationException(); } /** * Process the xmlfile named fileName with the given system * ID. * * @param stream * an input stream containing the xml content. * @param systemId * system ID. */ public void processStream(InputStream stream, String systemId) throws ValidationException, ParserConfigurationException, SAXException, IOException, SchedulerException, ClassNotFoundException, ParseException { clearValidationExceptions(); scheduledJobs.clear(); jobsToSchedule.clear(); calsToSchedule.clear(); getLog().info("Parsing XML from stream with systemId: " + systemId + " validating: " + digester.getValidating() + " validating schema: " + digester.getSchema()); InputSource is = new InputSource(stream); is.setSystemId(systemId); digester.push(this); digester.parse(is); maybeThrowValidationException(); } /** * Process the xml file in the default location, and schedule all of the * jobs defined within it. * */ public void processFileAndScheduleJobs(Scheduler sched, boolean overWriteExistingJobs) throws SchedulerException, Exception { processFileAndScheduleJobs(QUARTZ_XML_FILE_NAME, sched, overWriteExistingJobs); } /** * Process the xml file in the given location, and schedule all of the * jobs defined within it. * * @param fileName * meta data file name. */ public void processFileAndScheduleJobs(String fileName, Scheduler sched, boolean overWriteExistingJobs) throws Exception { processFileAndScheduleJobs(fileName, fileName, sched, overWriteExistingJobs); } /** * Process the xml file in the given location, and schedule all of the * jobs defined within it. * * @param fileName * meta data file name. */ public void processFileAndScheduleJobs(String fileName, String systemId, Scheduler sched, boolean overWriteExistingJobs) throws Exception { schedLocal.set(sched); try { processFile(fileName, systemId); scheduleJobs(getScheduledJobs(), sched, overWriteExistingJobs); } finally { schedLocal.set(null); } } /** * Add the Jobs and Triggers defined in the given map of JobSchedulingBundle * s to the given scheduler. * * @param jobBundles * @param sched * @param overWriteExistingJobs * @throws Exception */ public void scheduleJobs(Map jobBundles, Scheduler sched, boolean overWriteExistingJobs) throws Exception { getLog().info("Scheduling " + jobsToSchedule.size() + " parsed jobs."); Iterator itr = calsToSchedule.iterator(); while (itr.hasNext()) { CalendarBundle bndle = (CalendarBundle) itr.next(); addCalendar(sched, bndle); } itr = jobsToSchedule.iterator(); while (itr.hasNext()) { JobSchedulingBundle bndle = (JobSchedulingBundle) itr.next(); scheduleJob(bndle, sched, overWriteExistingJobs); } itr = listenersToSchedule.iterator(); while (itr.hasNext()) { JobListener listener = (JobListener) itr.next(); getLog().info("adding listener "+listener.getName()+" of class "+listener.getClass().getName()); sched.addJobListener(listener); } getLog().info(jobBundles.size() + " scheduled jobs."); } /** * Returns a Map of scheduled jobs. *

* The key is the job name and the value is a JobSchedulingBundle * containing the JobDetail and Trigger. * * @return a Map of scheduled jobs. */ public Map getScheduledJobs() { return Collections.unmodifiableMap(scheduledJobs); } /** * Returns a JobSchedulingBundle for the job name. * * @param name * job name. * @return a JobSchedulingBundle for the job name. */ public JobSchedulingBundle getScheduledJob(String name) { return (JobSchedulingBundle) getScheduledJobs().get(name); } /** * Returns an InputStream from the fileName as a resource. * * @param fileName * file name. * @return an InputStream from the fileName as a resource. */ protected InputStream getInputStream(String fileName) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); InputStream is = cl.getResourceAsStream(fileName); return is; } /** * Schedules a given job and trigger (both wrapped by a JobSchedulingBundle). * * @param job * job wrapper. * @exception SchedulerException * if the Job or Trigger cannot be added to the Scheduler, or * there is an internal Scheduler error. */ public void scheduleJob(JobSchedulingBundle job) throws SchedulerException { scheduleJob(job, (Scheduler) schedLocal.get(), getOverWriteExistingJobs()); } public void addJobToSchedule(JobSchedulingBundle job) { jobsToSchedule.add(job); } public void addCalendarToSchedule(CalendarBundle cal) { calsToSchedule.add(cal); } public void addListenerToSchedule(JobListener listener) { listenersToSchedule.add(listener); } /** * Schedules a given job and trigger (both wrapped by a JobSchedulingBundle). * * @param job * job wrapper. * @param sched * job scheduler. * @param localOverWriteExistingJobs * locally overwrite existing jobs. * @exception SchedulerException * if the Job or Trigger cannot be added to the Scheduler, or * there is an internal Scheduler error. */ public void scheduleJob(JobSchedulingBundle job, Scheduler sched, boolean localOverWriteExistingJobs) throws SchedulerException { if ((job != null) && job.isValid()) { JobDetail detail = job.getJobDetail(); JobDetail dupeJ = sched.getJobDetail(detail.getName(), detail.getGroup()); if ((dupeJ != null) && !localOverWriteExistingJobs) { getLog().info("Not overwriting existing job: " + dupeJ.getFullName()); return; } if (dupeJ != null) { getLog().info("Replacing job: " + detail.getFullName()); } else { getLog().info("Adding job: " + detail.getFullName()); } if (job.getTriggers().size() == 0 && !job.getJobDetail().isDurable()) { throw new SchedulerException("A Job defined without any triggers must be durable"); } sched.addJob(detail, true); for (Iterator iter = job.getTriggers().iterator(); iter.hasNext(); ) { Trigger trigger = (Trigger)iter.next(); Trigger dupeT = sched.getTrigger(trigger.getName(), trigger.getGroup()); trigger.setJobName(detail.getName()); trigger.setJobGroup(detail.getGroup()); if(trigger.getStartTime() == null) trigger.setStartTime(new Date()); if (dupeT != null) { getLog().debug( "Rescheduling job: " + detail.getFullName() + " with updated trigger: " + trigger.getFullName()); if(!dupeT.getJobGroup().equals(trigger.getJobGroup()) || !dupeT.getJobName().equals(trigger.getJobName())) getLog().warn("Possibly duplicately named triggers in jobs xml file!"); sched.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger); } else { getLog().debug( "Scheduling job: " + detail.getFullName() + " with trigger: " + trigger.getFullName()); sched.scheduleJob(trigger); } } addScheduledJob(job); } } /** * Adds a scheduled job. * * @param job * job wrapper. */ protected void addScheduledJob(JobSchedulingBundle job) { scheduledJobs.put(job.getFullName(), job); } /** * Adds a calendar. * * @param calendarBundle calendar bundle. * @throws SchedulerException if the Calendar cannot be added to the Scheduler, or * there is an internal Scheduler error. */ public void addCalendar(Scheduler sched, CalendarBundle calendarBundle) throws SchedulerException { sched.addCalendar( calendarBundle.getCalendarName(), calendarBundle.getCalendar(), calendarBundle.getReplace(), true); } /** * EntityResolver interface. *

* Allow the application to resolve external entities. *

* Until quartz.dtd has a public ID, it must resolved as a * system ID. Here's the order of resolution (if one fails, continue to the * next). *

    *
  1. Tries to resolve the systemId with ClassLoader.getResourceAsStream(String). *
  2. *
  3. If the systemId starts with QUARTZ_SYSTEM_ID_PREFIX, * then resolve the part after QUARTZ_SYSTEM_ID_PREFIX with * ClassLoader.getResourceAsStream(String).
  4. *
  5. Else try to resolve systemId as a URL. *
  6. If systemId has a colon in it, create a new URL *
  7. *
  8. Else resolve systemId as a File and * then call File.toURL().
  9. * *
*

* If the publicId does exist, resolve it as a URL. If the * publicId is the Quartz public ID, then resolve it locally. * * @param publicId * The public identifier of the external entity being referenced, * or null if none was supplied. * @param systemId * The system identifier of the external entity being referenced. * @return An InputSource object describing the new input source, or null * to request that the parser open a regular URI connection to the * system identifier. * @exception SAXException * Any SAX exception, possibly wrapping another exception. * @exception IOException * A Java-specific IO exception, possibly the result of * creating a new InputStream or Reader for the InputSource. */ public InputSource resolveEntity(String publicId, String systemId) { InputSource inputSource = null; InputStream is = null; URL url = null; try { if (publicId == null) { if (systemId != null) { // resolve Quartz Schema locally if (QUARTZ_SCHEMA.equals(systemId)) { is = getClass().getResourceAsStream(QUARTZ_DTD); } else { is = getInputStream(systemId); if (is == null) { int start = systemId.indexOf(QUARTZ_SYSTEM_ID_PREFIX); if (start > -1) { String fileName = systemId .substring(QUARTZ_SYSTEM_ID_PREFIX.length()); is = getInputStream(fileName); } else { if (systemId.indexOf(':') == -1) { File file = new java.io.File(systemId); url = file.toURL(); } else { url = new URL(systemId); } is = url.openStream(); } } } } } else { // resolve Quartz DTD locally if (QUARTZ_PUBLIC_ID.equals(publicId)) { is = getClass().getResourceAsStream(QUARTZ_DTD); } else { url = new URL(systemId); is = url.openStream(); } } } catch (Exception e) { e.printStackTrace(); } finally { if (is != null) { inputSource = new InputSource(is); inputSource.setPublicId(publicId); inputSource.setSystemId(systemId); } } return inputSource; } /** * ErrorHandler interface. * * Receive notification of a warning. * * @param exception * The error information encapsulated in a SAX parse exception. * @exception SAXException * Any SAX exception, possibly wrapping another exception. */ public void warning(SAXParseException e) throws SAXException { addValidationException(e); } /** * ErrorHandler interface. * * Receive notification of a recoverable error. * * @param exception * The error information encapsulated in a SAX parse exception. * @exception SAXException * Any SAX exception, possibly wrapping another exception. */ public void error(SAXParseException e) throws SAXException { addValidationException(e); } /** * ErrorHandler interface. * * Receive notification of a non-recoverable error. * * @param exception * The error information encapsulated in a SAX parse exception. * @exception SAXException * Any SAX exception, possibly wrapping another exception. */ public void fatalError(SAXParseException e) throws SAXException { addValidationException(e); } /** * Adds a detected validation exception. * * @param SAXException * SAX exception. */ protected void addValidationException(SAXException e) { validationExceptions.add(e); } /** * Resets the the number of detected validation exceptions. */ protected void clearValidationExceptions() { validationExceptions.clear(); } /** * Throws a ValidationException if the number of validationExceptions * detected is greater than zero. * * @exception ValidationException * DTD validation exception. */ protected void maybeThrowValidationException() throws ValidationException { if (validationExceptions.size() > 0) { throw new ValidationException(validationExceptions); } } /** * RuleSet for common Calendar tags. * * @author Chris Bonham */ public class CalendarRuleSet extends RuleSetBase { protected String prefix; protected String setNextMethodName; public CalendarRuleSet(String prefix, String setNextMethodName) { super(); this.prefix = prefix; this.setNextMethodName = setNextMethodName; } public void addRuleInstances(Digester digester) { digester.addObjectCreate(prefix, CalendarBundle.class); digester.addSetProperties(prefix, TAG_CLASS_NAME, "className"); digester.addBeanPropertySetter(prefix + "/" + TAG_NAME, "calendarName"); digester.addBeanPropertySetter(prefix + "/" + TAG_DESCRIPTION, "description"); digester.addSetNext(prefix, setNextMethodName); } } /** * RuleSet for common Trigger tags. * * @author Chris Bonham */ public class TriggerRuleSet extends RuleSetBase { protected String prefix; protected Class clazz; public TriggerRuleSet(String prefix, Class clazz) { super(); this.prefix = prefix; if (!Trigger.class.isAssignableFrom(clazz)) { throw new IllegalArgumentException("Class must be an instance of Trigger"); } this.clazz = clazz; } public void addRuleInstances(Digester digester) { digester.addObjectCreate(prefix, clazz); digester.addBeanPropertySetter(prefix + "/" + TAG_NAME, "name"); digester.addBeanPropertySetter(prefix + "/" + TAG_GROUP, "group"); digester.addBeanPropertySetter(prefix + "/" + TAG_DESCRIPTION, "description"); digester.addBeanPropertySetter(prefix + "/" + TAG_VOLATILITY, "volatility"); digester.addRule(prefix + "/" + TAG_MISFIRE_INSTRUCTION, new MisfireInstructionRule("misfireInstruction")); digester.addBeanPropertySetter(prefix + "/" + TAG_CALENDAR_NAME, "calendarName"); digester.addBeanPropertySetter(prefix + "/" + TAG_JOB_NAME, "jobName"); digester.addBeanPropertySetter(prefix + "/" + TAG_JOB_GROUP, "jobGroup"); Converter converter = new DateConverter(new String[] { XSD_DATE_FORMAT, DTD_DATE_FORMAT }); digester.addRule(prefix + "/" + TAG_START_TIME, new SimpleConverterRule("startTime", converter, Date.class)); digester.addRule(prefix + "/" + TAG_END_TIME, new SimpleConverterRule("endTime", converter, Date.class)); } } /** * This rule is needed to fix QUARTZ-153. *

* Since the Jakarta Commons BeanUtils 1.6.x ConvertUtils class uses static utility * methods, the DateConverter and TimeZoneConverter were * overriding any previously registered converters for java.util.Date and * java.util.TimeZone. *

* Jakarta Commons BeanUtils 1.7.x fixes this issue by internally using per-context-classloader * pseudo-singletons (see * http://jakarta.apache.org/commons/beanutils/commons-beanutils-1.7.0/RELEASE-NOTES.txt). * This ensures web applications in the same JVM are using independent converters * based on their classloaders. However, the environment for QUARTZ-153 started Quartz * using the QuartzInitializationServlet which started JobInitializationPlugin. * In this case, the web classloader instances would be the same. *

* To make sure the converters aren't overridden by the JobSchedulingDataProcessor, * it's easier to just override BeanPropertySetterRule.end() to convert the * body text to the specified class using the specified converter. * * @author Chris Bonham */ public class SimpleConverterRule extends BeanPropertySetterRule { private Converter converter; private Class clazz; /** *

Construct rule that sets the given property from the body text.

* * @param propertyName name of property to set * @param converter converter to use * @param clazz class to convert to */ public SimpleConverterRule(String propertyName, Converter converter, Class clazz) { this.propertyName = propertyName; if (converter == null) { throw new IllegalArgumentException("Converter must not be null"); } this.converter = converter; if (clazz == null) { throw new IllegalArgumentException("Class must not be null"); } this.clazz = clazz; } /** * Process the end of this element. * * @param namespace the namespace URI of the matching element, or an * empty string if the parser is not namespace aware or the element has * no namespace * @param name the local name if the parser is namespace aware, or just * the element name otherwise * * @exception NoSuchMethodException if the bean does not * have a writeable property of the specified name */ public void end(String namespace, String name) throws Exception { String property = propertyName; if (property == null) { // If we don't have a specific property name, // use the element name. property = name; } // Get a reference to the top object Object top = this.digester.peek(); // log some debugging information if (getDigester().getLogger().isDebugEnabled()) { getDigester().getLogger().debug("[BeanPropertySetterRule]{" + getDigester().getMatch() + "} Set " + top.getClass().getName() + " property " + property + " with text " + bodyText); } // Force an exception if the property does not exist // (BeanUtils.setProperty() silently returns in this case) if (top instanceof DynaBean) { DynaProperty desc = ((DynaBean) top).getDynaClass().getDynaProperty(property); if (desc == null) { throw new NoSuchMethodException ("Bean has no property named " + property); } } else /* this is a standard JavaBean */ { PropertyDescriptor desc = PropertyUtils.getPropertyDescriptor(top, property); if (desc == null) { throw new NoSuchMethodException ("Bean has no property named " + property); } } // Set the property only using this converter Object value = converter.convert(clazz, bodyText); PropertyUtils.setProperty(top, property, value); } } /** * This rule translates the trigger misfire instruction constant name into its * corresponding value. * * @TODO Consider removing this class and using a * com.google.appengine.repackaged.org.apache.commons.digester.Substitutor strategy once * Jakarta Commons Digester 1.6 is final. * * @author Chris Bonham */ public class MisfireInstructionRule extends BeanPropertySetterRule { /** *

Construct rule that sets the given property from the body text.

* * @param propertyName name of property to set */ public MisfireInstructionRule(String propertyName) { this.propertyName = propertyName; } /** * Process the body text of this element. * * @param namespace the namespace URI of the matching element, or an * empty string if the parser is not namespace aware or the element has * no namespace * @param name the local name if the parser is namespace aware, or just * the element name otherwise * @param text The text of the body of this element */ public void body(String namespace, String name, String text) throws Exception { super.body(namespace, name, text); this.bodyText = getConstantValue(bodyText); } /** * Returns the value for the constant name. * If the constant can't be found or any exceptions occur, * return 0. * * @param constantName constant name. * @return the value for the constant name. */ private String getConstantValue(String constantName) { String value = "0"; Object top = this.digester.peek(); if (top != null) { Class clazz = top.getClass(); try { java.lang.reflect.Field field = clazz.getField(constantName); Object fieldValue = field.get(top); if (fieldValue != null) { value = fieldValue.toString(); } } catch (Exception e) { // ignore } } return value; } } /** *

Standard {@link Converter} implementation that converts an incoming * String into a java.util.Date object, optionally using a * default value or throwing a {@link ConversionException} if a conversion * error occurs.

*/ public final class DateConverter implements Converter { // ----------------------------------------------------------- Constructors /** * Create a {@link Converter} that will throw a {@link ConversionException} * if a conversion error occurs. */ public DateConverter() { this.defaultValue = null; this.useDefault = false; } /** * Create a {@link Converter} that will return the specified default value * if a conversion error occurs. * * @param defaultValue The default value to be returned */ public DateConverter(Object defaultValue) { this.defaultValue = defaultValue; this.useDefault = true; } public DateConverter(String[] formats) { this(); int len = formats.length; dateFormats = new DateFormat[len]; for (int i = 0; i < len; i++) { dateFormats[i] = new SimpleDateFormat(formats[i]); } } // ----------------------------------------------------- Instance Variables /** * The default value specified to our Constructor, if any. */ private Object defaultValue = null; /** * Should we return the default value on conversion errors? */ private boolean useDefault = true; private DateFormat[] dateFormats; // --------------------------------------------------------- Public Methods /** * Convert the specified input object into an output object of the * specified type. * * @param type Data type to which this value should be converted * @param value The input value to be converted * * @exception ConversionException if conversion cannot be performed * successfully */ public Object convert(Class type, Object value) { if (value == null) { if (useDefault) { return (defaultValue); } else { return (null); } } if (String.class.equals(type)) { if ((value instanceof Date) && (dateFormats != null)) { return (dateFormats[0].format((Date) value)); } else { return (value.toString()); } } if (value instanceof Date) { return (value); } try { if (Date.class.isAssignableFrom(type) && dateFormats != null) { return parseDate(value); } else { return (value.toString()); } } catch (Exception e) { if (useDefault) { return (defaultValue); } else { throw new ConversionException(e); } } } protected Date parseDate(Object value) throws ParseException { Date date = null; int len = dateFormats.length; for (int i = 0; i < len; i++) { try { date = (dateFormats[i].parse(value.toString())); break; } catch (ParseException e) { // if this is the last format, throw the exception if (i == (len - 1)) { throw e; } } } return date; } } /** *

Standard {@link Converter} implementation that converts an incoming * String into a java.util.TimeZone object throwing a * {@link ConversionException} if a conversion error occurs.

*/ public final class TimeZoneConverter implements Converter { // ----------------------------------------------------------- Constructors /** * Create a {@link Converter} that will throw a {@link ConversionException} * if a conversion error occurs. */ public TimeZoneConverter() { } // --------------------------------------------------------- Public Methods /** * Convert the specified input object into an output object of the * specified type. * * @param type Data type to which this value should be converted * @param value The input value to be converted * * @exception ConversionException if conversion cannot be performed * successfully */ public Object convert(Class type, Object value) { if (value == null) { return (null); } if (value instanceof TimeZone) { return (value); } try { if (String.class.equals(value.getClass())) { return (TimeZone.getTimeZone((String) value)); } else { return (value.toString()); } } catch (Exception e) { throw new ConversionException(e); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy