Commits

jhouse  committed e414acb

merging 1.4.5 changes.

git-svn-id: http://svn.opensymphony.com/svn/quartz/trunk@4069f7d36a-ea1c-0410-88ea-9fd03e4c9665

  • Participants
  • Parent commits 6b8d46d

Comments (0)

Files changed (7)

File quartz_1x/docs/dbTables/tables_db2_v72.sql

+--
+-- Thanks to Horia Muntean for submitting this, Mikkel Heisterberg for updating it 
+--
+-- .. known to work with DB2 7.2 and the JDBC driver "COM.ibm.db2.jdbc.net.DB2Driver"
+-- .. likely to work with others...
+--
+-- In your Quartz properties file, you'll need to set 
+-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.DB2v7Delegate
+--
+-- or
+--
+-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+--
+-- If you're using DB2 6.x you'll want to set this property to
+-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.DB2v6Delegate
+--
+-- Note that the blob column size (e.g. blob(2000)) dictates the amount of data that can be stored in 
+-- that blob - i.e. limits the amount of data you can put into your JobDataMap 
+--
+
+DROP TABLE QRTZ_JOB_LISTENERS;
+DROP TABLE QRTZ_TRIGGER_LISTENERS;
+DROP TABLE QRTZ_FIRED_TRIGGERS;
+DROP TABLE QRTZ_PAUSED_TRIGGER_GRPS;
+DROP TABLE QRTZ_SCHEDULER_STATE;
+DROP TABLE QRTZ_LOCKS;
+DROP TABLE QRTZ_SIMPLE_TRIGGERS;
+DROP TABLE QRTZ_CRON_TRIGGERS;
+DROP TABLE QRTZ_TRIGGERS;
+DROP TABLE QRTZ_JOB_DETAILS;
+DROP TABLE QRTZ_CALENDARS;
+DROP TABLE QRTZ_BLOB_TRIGGERS;
+
+create table qrtz_job_details (
+  job_name varchar(80) not null,
+  job_group varchar(80) not null,
+  description varchar(120),
+  job_class_name varchar(128) not null,
+  is_durable varchar(1) not null,
+  is_volatile varchar(1) not null,
+  is_stateful varchar(1) not null,
+  requests_recovery varchar(1) not null,
+  job_data blob(2000),
+    primary key (job_name,job_group)
+);
+
+create table qrtz_job_listeners(
+  job_name varchar(80) not null,
+  job_group varchar(80) not null,
+  job_listener varchar(80) not null,
+    primary key (job_name,job_group,job_listener),
+    foreign key (job_name,job_group) references qrtz_job_details(job_name,job_group)
+);
+
+create table qrtz_triggers(
+  trigger_name varchar(80) not null,
+  trigger_group varchar(80) not null,
+  job_name varchar(80) not null,
+  job_group varchar(80) not null,
+  is_volatile varchar(1) not null,
+  description varchar(120),
+  next_fire_time bigint,
+  prev_fire_time bigint,
+  trigger_state varchar(16) not null,
+  trigger_type varchar(8) not null,
+  start_time bigint not null,
+  end_time bigint,
+  calendar_name varchar(80),
+  misfire_instr smallint,
+    primary key (trigger_name,trigger_group),
+    foreign key (job_name,job_group) references qrtz_job_details(job_name,job_group)
+);
+
+create table qrtz_simple_triggers(
+  trigger_name varchar(80) not null,
+  trigger_group varchar(80) not null,
+  repeat_count bigint not null,
+  repeat_interval bigint not null,
+  times_triggered bigint not null,
+    primary key (trigger_name,trigger_group),
+    foreign key (trigger_name,trigger_group) references qrtz_triggers(trigger_name,trigger_group)
+);
+
+create table qrtz_cron_triggers(
+  trigger_name varchar(80) not null,
+  trigger_group varchar(80) not null,
+  cron_expression varchar(80) not null,
+  time_zone_id varchar(80),
+    primary key (trigger_name,trigger_group),
+    foreign key (trigger_name,trigger_group) references qrtz_triggers(trigger_name,trigger_group)
+);
+
+create table qrtz_blob_triggers(
+  trigger_name varchar(80) not null,
+  trigger_group varchar(80) not null,
+  blob_data blob(2000),
+    primary key (trigger_name,trigger_group),
+    foreign key (trigger_name,trigger_group) references qrtz_triggers(trigger_name,trigger_group)
+);
+
+create table qrtz_trigger_listeners(
+  trigger_name varchar(80) not null,
+  trigger_group varchar(80) not null,
+  trigger_listener varchar(80) not null,
+    primary key (trigger_name,trigger_group,trigger_listener),
+    foreign key (trigger_name,trigger_group) references qrtz_triggers(trigger_name,trigger_group)
+);
+
+create table qrtz_calendars(
+  calendar_name varchar(80) not null,
+  calendar blob(2000) not null,
+    primary key (calendar_name)
+);
+
+create table qrtz_fired_triggers(
+  entry_id varchar(95) not null,
+  trigger_name varchar(80) not null,
+  trigger_group varchar(80) not null,
+  is_volatile varchar(1) not null,
+  instance_name varchar(80) not null,
+  fired_time bigint not null,
+  state varchar(16) not null,
+  job_name varchar(80),
+  job_group varchar(80),
+  is_stateful varchar(1),
+  requests_recovery varchar(1),
+    primary key (entry_id)
+);
+
+
+create table qrtz_paused_trigger_grps(
+  trigger_group  varchar(80) not null, 
+    primary key (trigger_group)
+);
+
+create table qrtz_scheduler_state (
+  instance_name varchar(80) not null,
+  last_checkin_time bigint not null,
+  checkin_interval bigint not null,
+  recoverer varchar(80),
+    primary key (instance_name)
+);
+
+create table qrtz_locks
+  (
+    lock_name  varchar(40) not null, 
+      primary key (lock_name)
+);
+
+insert into qrtz_locks values('TRIGGER_ACCESS');
+insert into qrtz_locks values('JOB_ACCESS');
+insert into qrtz_locks values('CALENDAR_ACCESS');
+insert into qrtz_locks values('STATE_ACCESS');
+insert into qrtz_locks values('MISFIRE_ACCESS');
+

File quartz_1x/docs/dbTables/tables_oracle.sql

 INSERT INTO qrtz_locks values('MISFIRE_ACCESS');
 create index idx_qrtz_j_req_recovery on qrtz_job_details(REQUESTS_RECOVERY);
 create index idx_qrtz_t_next_fire_time on qrtz_triggers(NEXT_FIRE_TIME);
-create index idx_qrtz_t_next_state on qrtz_triggers(TRIGGER_STATE);
+create index idx_qrtz_t_state on qrtz_triggers(TRIGGER_STATE);
+create index idx_qrtz_t_nft_st on qrtz_triggers(NEXT_FIRE_TIME,TRIGGER_STATE);
 create index idx_qrtz_t_volatile on qrtz_triggers(IS_VOLATILE);
 create index idx_qrtz_ft_trig_name on qrtz_fired_triggers(TRIGGER_NAME);
 create index idx_qrtz_ft_trig_group on qrtz_fired_triggers(TRIGGER_GROUP);
+create index idx_qrtz_ft_trig_nm_gp on qrtz_fired_triggers(TRIGGER_NAME,TRIGGER_GROUP);
 create index idx_qrtz_ft_trig_volatile on qrtz_fired_triggers(IS_VOLATILE);
 create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(INSTANCE_NAME);
 create index idx_qrtz_ft_job_name on qrtz_fired_triggers(JOB_NAME);

File quartz_1x/src/java/main/org/quartz/Trigger.java

 
         if (otherTime == null) return -1;
 
-        return myTime.compareTo(otherTime);
+        if(myTime.before(otherTime))
+            return -1;
+
+        if(myTime.after(otherTime))
+            return 1;
+        
+        return 0;
     }
 
     public boolean equals(Object obj) {

File quartz_1x/src/java/main/org/quartz/impl/jdbcjobstore/JobStoreSupport.java

 
             logWarnIfNonZero(failedInstances.size(),
                     "ClusterManager: detected " + failedInstances.size()
-                            + " failed instances.");
+                            + " failed or restarted instances.");
             try {
                 Iterator itr = failedInstances.iterator();
                 while (itr.hasNext()) {

File quartz_1x/src/java/main/org/quartz/xml/JobSchedulingDataProcessor.java

  * All rights reserved.
  * 
  * 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 javax.xml.parsers.ParserConfigurationException;
 
 import org.apache.commons.beanutils.ConversionException;
-import org.apache.commons.beanutils.ConvertUtils;
 import org.apache.commons.beanutils.Converter;
+import org.apache.commons.beanutils.DynaBean;
+import org.apache.commons.beanutils.DynaProperty;
+import org.apache.commons.beanutils.PropertyUtils;
 import org.apache.commons.digester.BeanPropertySetterRule;
 import org.apache.commons.digester.Digester;
 import org.apache.commons.digester.RuleSetBase;
 
     public static final String QUARTZ_SYSTEM_ID = "http://www.quartzscheduler.org/dtd/job_scheduling_data_1_0.dtd";
     
-    public static final String QUARTZ_DTD = "job_scheduling_data_1_0.dtd";
+    public static final String QUARTZ_DTD = "/org/quartz/xml/job_scheduling_data_1_0.dtd";
     
     public static final String QUARTZ_NS = "http://www.quartzscheduler.org/ns/quartz";
     
     public static final String QUARTZ_SCHEMA = "http://www.quartzscheduler.org/ns/quartz/job_scheduling_data_1_1.xsd";
     
-    public static final String QUARTZ_XSD = "job_scheduling_data_1_1.xsd";
+    public static final String QUARTZ_XSD = "/org/quartz/xml/job_scheduling_data_1_1.xsd";
 
     public static final String QUARTZ_SYSTEM_ID_DIR_PROP = "quartz.system.id.dir";
 
      * Constructor for QuartzMetaDataProcessor.
      */
     public JobSchedulingDataProcessor() {
-        this(true, true);
+        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 validating, boolean validatingSchema) {
-        initDigester(validating, validatingSchema);
+    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 validating, boolean validatingSchema) {
+    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);
         
-        ConvertUtils.register(new DateConverter(new String[] { XSD_DATE_FORMAT, DTD_DATE_FORMAT }), Date.class);
-        ConvertUtils.register(new TimeZoneConverter(), TimeZone.class);
-        
         digester.addSetProperties(TAG_QUARTZ, TAG_OVERWRITE_EXISTING_JOBS, "overWriteExistingJobs");
         digester.addRuleSet(new CalendarRuleSet(TAG_QUARTZ + "/" + TAG_CALENDAR, "addCalendarToSchedule"));
         digester.addRuleSet(new CalendarRuleSet("*/" + TAG_BASE_CALENDAR, "setBaseCalendar"));
         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.addBeanPropertySetter(TAG_QUARTZ + "/" + TAG_JOB + "/" + TAG_TRIGGER + "/" + TAG_CRON + "/" + TAG_TIME_ZONE, "timeZone");
+        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");
     }
     }
 
     /**
+     * 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 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, fileName);
+            processFile(fileName, systemId);
         scheduleJobs(getScheduledJobs(), sched, overWriteExistingJobs);
         } finally {
             schedLocal.set(null);
                 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 {
             digester.addBeanPropertySetter(prefix + "/" + TAG_CALENDAR_NAME, "calendarName");
             digester.addBeanPropertySetter(prefix + "/" + TAG_JOB_NAME, "jobName");
             digester.addBeanPropertySetter(prefix + "/" + TAG_JOB_GROUP, "jobGroup");
-            digester.addBeanPropertySetter(prefix + "/" + TAG_START_TIME, "startTime");
-            digester.addBeanPropertySetter(prefix + "/" + TAG_END_TIME, "endTime");
+            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 <a href="http://jira.opensymphony.com/browse/QUARTZ-153">QUARTZ-153</a>.
+     * <p>
+     * Since the Jakarta Commons BeanUtils 1.6.x <code>ConvertUtils</code> class uses static utility 
+     * methods, the <code>DateConverter</code> and <code>TimeZoneConverter</code> were
+     * overriding any previously registered converters for <code>java.util.Date</code> and
+     * <code>java.util.TimeZone</code>.
+     * <p>
+     * Jakarta Commons BeanUtils 1.7.x fixes this issue by internally using per-context-classloader
+     * pseudo-singletons (see <a href="http://jakarta.apache.org/commons/beanutils/commons-beanutils-1.7.0/RELEASE-NOTES.txt">
+     * http://jakarta.apache.org/commons/beanutils/commons-beanutils-1.7.0/RELEASE-NOTES.txt</a>).
+     * 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 <code>QuartzInitializationServlet</code> which started <code>JobInitializationPlugin</code>.  
+     * In this case, the web classloader instances would be the same.
+     * <p>
+     * To make sure the converters aren't overridden by the <code>JobSchedulingDataProcessor</code>,
+     * it's easier to just override <code>BeanPropertySetterRule.end()</code> to convert the
+     * body text to the specified class using the specified converter.
+     * 
+     * @author <a href="mailto:bonhamcm@thirdeyeconsulting.com">Chris Bonham</a>
+     */
+    public class SimpleConverterRule extends BeanPropertySetterRule {
+        private Converter converter;
+        private Class clazz;
+        
+        /**
+         * <p>Construct rule that sets the given property from the body text.</p>
+         *
+         * @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);
         }
     }
     

File quartz_1x/src/java/plugins/org/quartz/plugins/management/ShutdownHookPlugin.java

             public void run() {
                 getLog().info("Shutting down Quartz...");
                 try {
-                    scheduler.shutdown(true);
+                    scheduler.shutdown(isCleanShutdown());
                 } catch (SchedulerException e) {
                     getLog().info(
                             "Error shutting down Quartz: " + e.getMessage(), e);

File quartz_1x/src/java/plugins/org/quartz/plugins/xml/JobInitializationPlugin.java

     
     private String filePath = null;
     
+    private boolean useContextClassLoader = true;
+    
     private boolean validating = true;
     
     private boolean validatingSchema = true;
     }
     
     /**
+     * Whether or not the context class loader should be used. Default is <code>true</code>.
+     * 
+     * @return
+     */
+    public boolean isUseContextClassLoader() {
+        return useContextClassLoader;
+    }
+
+    /**
+     * Whether or not context class loader should be used. Default is <code>true</code>.
+     * 
+     * @param useContextClassLoader
+     */
+    public void setUseContextClassLoader(boolean useContextClassLoader) {
+        this.useContextClassLoader = useContextClassLoader;
+    }
+    
+    /**
      * Whether or not the XML should be validated. Default is <code>true</code>.
      * 
      * @return
             getLog().info("Registering Quartz Job Initialization Plug-in.");
             
             findFile();
-            
-            if(scanInterval > 0) {
-                SimpleTrigger trig = new SimpleTrigger(
-                        "JobInitializationPlugin_"+name, 
-                        "JobInitializationPlugin", 
-                        new Date(), null, 
-                        SimpleTrigger.REPEAT_INDEFINITELY, scanInterval);
-                trig.setVolatility(true);
-                JobDetail job = new JobDetail(
-                        "JobInitializationPlugin_"+name, 
-                        "JobInitializationPlugin",
-                        FileScanJob.class);
-                job.setVolatility(true);
-                job.getJobDataMap().put(FileScanJob.FILE_NAME, getFilePath());
-                job.getJobDataMap().put(FileScanJob.FILE_SCAN_LISTENER_NAME, "JobInitializationPlugin_"+name);
-                
-                scheduler.getContext().put("JobInitializationPlugin_"+name, this);
-                scheduler.scheduleJob(job, trig);
-            }
         }
         finally {
             initializing = false;
     }
 
     public void start() {
+
+        if(scanInterval > 0) {
+            try{
+                SimpleTrigger trig = new SimpleTrigger(
+                        "JobInitializationPlugin_"+name, 
+                        "JobInitializationPlugin", 
+                        new Date(), null, 
+                        SimpleTrigger.REPEAT_INDEFINITELY, scanInterval);
+                trig.setVolatility(true);
+                JobDetail job = new JobDetail(
+                        "JobInitializationPlugin_"+name, 
+                        "JobInitializationPlugin",
+                        FileScanJob.class);
+                job.setVolatility(true);
+                job.getJobDataMap().put(FileScanJob.FILE_NAME, getFilePath());
+                job.getJobDataMap().put(FileScanJob.FILE_SCAN_LISTENER_NAME, "JobInitializationPlugin_"+name);
+                
+                scheduler.getContext().put("JobInitializationPlugin_"+name, this);
+                scheduler.scheduleJob(job, trig);
+            }
+            catch(SchedulerException se) {
+                getLog().error("Error starting background-task for watching jobs file.", se);
+            }
+        }
+        
         try {
             processFile();
         }
         if (!fileFound) return;
 
         JobSchedulingDataProcessor processor = 
-            new JobSchedulingDataProcessor(isValidating(), isValidatingSchema());
+            new JobSchedulingDataProcessor(isUseContextClassLoader(), isValidating(), isValidatingSchema());
 
         try {
             processor.processFileAndScheduleJobs(fileName, scheduler, true);