diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/AdaptableJobFactory.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/AdaptableJobFactory.java index caddf3584c1..aa40358ca9f 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/AdaptableJobFactory.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/AdaptableJobFactory.java @@ -30,7 +30,7 @@ import org.springframework.util.ReflectionUtils; * JobFactory implementation that supports {@link java.lang.Runnable} * objects as well as standard Quartz {@link org.quartz.Job} instances. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. * * @author Juergen Hoeller * @since 2.0 diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/CronTriggerBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/CronTriggerBean.java index dc3b9dffc2b..cee2683dce5 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/CronTriggerBean.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/CronTriggerBean.java @@ -33,7 +33,7 @@ import org.springframework.util.Assert; * Convenience subclass of Quartz's {@link org.quartz.CronTrigger} class, * making bean-style usage easier. * - *

CronTrigger itself is already a JavaBean but lacks sensible defaults. + *

CronTrigger itself is already a JavaBean but lacks sensible defaults. * This class uses the Spring bean name as job name, the Quartz default group * ("DEFAULT") as job group, the current time as start time, and indefinite * repetition, if not specified. @@ -44,8 +44,10 @@ import org.springframework.util.Assert; * instead of registering the JobDetail separately. * *

NOTE: This convenience subclass does not work against Quartz 2.0. - * Use Quartz 2.0's native CronTriggerImpl class or the new - * Quartz 2.0 builder API instead. + * Use Quartz 2.0's native JobDetailImpl class or the new Quartz 2.0 + * builder API instead. Alternatively, switch to Spring's {@link CronTriggerFactoryBean} + * which largely is a drop-in replacement for this class and its properties and + * consistently works against Quartz 1.x as well as Quartz 2.0/2.1. * * @author Juergen Hoeller * @since 18.02.2004 @@ -72,6 +74,7 @@ public class CronTriggerBean extends CronTrigger private long startDelay; + /** * Register objects in the JobDataMap via a given Map. *

These objects will be available to this Trigger only, @@ -80,7 +83,7 @@ public class CronTriggerBean extends CronTrigger * (for example Spring-managed beans) * @see JobDetailBean#setJobDataAsMap */ - public void setJobDataAsMap(Map jobDataAsMap) { + public void setJobDataAsMap(Map jobDataAsMap) { getJobDataMap().putAll(jobDataAsMap); } @@ -110,6 +113,19 @@ public class CronTriggerBean extends CronTrigger } } + /** + * Set the start delay in milliseconds. + *

The start delay is added to the current system time (when the bean starts) + * to control the {@link #setStartTime start time} of the trigger. + *

If the start delay is non-zero, it will always + * take precedence over start time. + * @param startDelay the start delay, in milliseconds + */ + public void setStartDelay(long startDelay) { + Assert.state(startDelay >= 0, "Start delay cannot be negative."); + this.startDelay = startDelay; + } + /** * Set the JobDetail that this trigger should be associated with. *

This is typically used with a bean reference if the JobDetail @@ -122,20 +138,6 @@ public class CronTriggerBean extends CronTrigger this.jobDetail = jobDetail; } - /** - * Set the start delay in milliseconds. - *

The start delay is added to the current system time - * (when the bean starts) to control the {@link #setStartTime start time} - * of the trigger. - *

If the start delay is non-zero it will always - * take precedence over start time. - * @param startDelay the start delay, in milliseconds. - */ - public void setStartDelay(long startDelay) { - Assert.state(startDelay >= 0, "Start delay cannot be negative."); - this.startDelay = startDelay; - } - public JobDetail getJobDetail() { return this.jobDetail; } @@ -146,7 +148,7 @@ public class CronTriggerBean extends CronTrigger public void afterPropertiesSet() throws Exception { - if(this.startDelay > 0) { + if (this.startDelay > 0) { setStartTime(new Date(System.currentTimeMillis() + this.startDelay)); } diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/CronTriggerFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/CronTriggerFactoryBean.java new file mode 100644 index 00000000000..a8294f05d41 --- /dev/null +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/CronTriggerFactoryBean.java @@ -0,0 +1,283 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * 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. + */ + +package org.springframework.scheduling.quartz; + +import java.lang.reflect.Method; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; + +import org.quartz.CronTrigger; +import org.quartz.JobDataMap; +import org.quartz.JobDetail; +import org.quartz.Scheduler; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.Constants; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * A Spring {@link FactoryBean} for creating a Quartz {@link org.quartz.CronTrigger} + * instance, supporting bean-style usage for trigger configuration. + * + *

CronTrigger(Impl) itself is already a JavaBean but lacks sensible defaults. + * This class uses the Spring bean name as job name, the Quartz default group ("DEFAULT") + * as job group, the current time as start time, and indefinite repetition, if not specified. + * + *

This class will also register the trigger with the job name and group of + * a given {@link org.quartz.JobDetail}. This allows {@link SchedulerFactoryBean} + * to automatically register a trigger for the corresponding JobDetail, + * instead of registering the JobDetail separately. + * + *

NOTE: This FactoryBean works against both Quartz 1.x and Quartz 2.0/2.1, + * in contrast to the older {@link CronTriggerBean} class. + * + * @author Juergen Hoeller + * @since 3.1 + * @see #setName + * @see #setGroup + * @see #setStartTime + * @see #setJobName + * @see #setJobGroup + * @see #setJobDetail + * @see org.springframework.scheduling.quartz.SchedulerFactoryBean#setTriggers + * @see org.springframework.scheduling.quartz.SchedulerFactoryBean#setJobDetails + * @see org.springframework.scheduling.quartz.SimpleTriggerBean + */ +public class CronTriggerFactoryBean implements FactoryBean, BeanNameAware, InitializingBean { + + /** Constants for the CronTrigger class */ + private static final Constants constants = new Constants(CronTrigger.class); + + + private String name; + + private String group; + + private JobDetail jobDetail; + + private JobDataMap jobDataMap = new JobDataMap(); + + private Date startTime; + + private long startDelay; + + private String cronExpression; + + private TimeZone timeZone; + + private int priority; + + private int misfireInstruction; + + private String beanName; + + private CronTrigger cronTrigger; + + + /** + * Specify the trigger's name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Specify the trigger's group. + */ + public void setGroup(String group) { + this.group = group; + } + + /** + * Set the JobDetail that this trigger should be associated with. + */ + public void setJobDetail(JobDetail jobDetail) { + this.jobDetail = jobDetail; + } + + /** + * Set the trigger's JobDataMap. + * @see #setJobDataAsMap + */ + public void setJobDataMap(JobDataMap jobDataMap) { + this.jobDataMap = jobDataMap; + } + + /** + * Return the trigger's JobDataMap. + */ + public JobDataMap getJobDataMap() { + return this.jobDataMap; + } + + /** + * Register objects in the JobDataMap via a given Map. + *

These objects will be available to this Trigger only, + * in contrast to objects in the JobDetail's data map. + * @param jobDataAsMap Map with String keys and any objects as values + * (for example Spring-managed beans) + * @see org.springframework.scheduling.quartz.JobDetailBean#setJobDataAsMap + */ + public void setJobDataAsMap(Map jobDataAsMap) { + this.jobDataMap.putAll(jobDataAsMap); + } + + /** + * Set the start delay in milliseconds. + *

The start delay is added to the current system time (when the bean starts) + * to control the start time of the trigger. + * @param startDelay the start delay, in milliseconds + */ + public void setStartDelay(long startDelay) { + Assert.state(startDelay >= 0, "Start delay cannot be negative."); + this.startDelay = startDelay; + } + + /** + * Specify the cron expression for this trigger. + */ + public void setCronExpression(String cronExpression) { + this.cronExpression = cronExpression; + } + + /** + * Specify the time zone for this trigger's cron expression. + */ + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; + } + + /** + * Specify the priority of this trigger. + */ + public void setPriority(int priority) { + this.priority = priority; + } + + /** + * Specify a misfire instruction for this trigger. + */ + public void setMisfireInstruction(int misfireInstruction) { + this.misfireInstruction = misfireInstruction; + } + + /** + * Set the misfire instruction via the name of the corresponding + * constant in the {@link org.quartz.CronTrigger} class. + * Default is MISFIRE_INSTRUCTION_SMART_POLICY. + * @see org.quartz.CronTrigger#MISFIRE_INSTRUCTION_FIRE_ONCE_NOW + * @see org.quartz.CronTrigger#MISFIRE_INSTRUCTION_DO_NOTHING + * @see org.quartz.Trigger#MISFIRE_INSTRUCTION_SMART_POLICY + */ + public void setMisfireInstructionName(String constantName) { + this.misfireInstruction = constants.asNumber(constantName).intValue(); + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + + public void afterPropertiesSet() { + if (this.name == null) { + this.name = this.beanName; + } + if (this.group == null) { + this.group = Scheduler.DEFAULT_GROUP; + } + if (this.jobDetail != null) { + this.jobDataMap.put(JobDetailAwareTrigger.JOB_DETAIL_KEY, this.jobDetail); + } + if (this.startDelay > 0) { + this.startTime = new Date(System.currentTimeMillis() + this.startDelay); + } + else if (this.startTime == null) { + this.startTime = new Date(); + } + if (this.timeZone == null) { + this.timeZone = TimeZone.getDefault(); + } + + /* + CronTriggerImpl cti = new CronTriggerImpl(); + cti.setName(this.name); + cti.setGroup(this.group); + cti.setJobKey(this.jobDetail.getKey()); + cti.setJobDataMap(this.jobDataMap); + cti.setStartTime(this.startTime); + cti.setCronExpression(this.cronExpression); + cti.setTimeZone(this.timeZone); + cti.setPriority(this.priority); + cti.setMisfireInstruction(this.misfireInstruction); + this.cronTrigger = cti; + */ + + Class cronTriggerClass; + Method jobKeyMethod; + try { + cronTriggerClass = getClass().getClassLoader().loadClass("org.quartz.impl.triggers.CronTriggerImpl"); + jobKeyMethod = JobDetail.class.getMethod("getKey"); + } + catch (ClassNotFoundException ex) { + cronTriggerClass = CronTrigger.class; + jobKeyMethod = null; + } + catch (NoSuchMethodException ex) { + throw new IllegalStateException("Incompatible Quartz version"); + } + BeanWrapper bw = new BeanWrapperImpl(cronTriggerClass); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("name", this.name); + pvs.add("group", this.group); + if (jobKeyMethod != null) { + pvs.add("jobKey", ReflectionUtils.invokeMethod(jobKeyMethod, this.jobDetail)); + } + else { + pvs.add("jobName", this.jobDetail.getName()); + pvs.add("jobGroup", this.jobDetail.getGroup()); + } + pvs.add("jobDataMap", this.jobDataMap); + pvs.add("startTime", this.startTime); + pvs.add("cronExpression", this.cronExpression); + pvs.add("timeZone", this.timeZone); + pvs.add("priority", this.priority); + pvs.add("misfireInstruction", this.misfireInstruction); + bw.setPropertyValues(pvs); + this.cronTrigger = (CronTrigger) bw.getWrappedInstance(); + } + + + public CronTrigger getObject() { + return this.cronTrigger; + } + + public Class getObjectType() { + return CronTrigger.class; + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/JobDetailAwareTrigger.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/JobDetailAwareTrigger.java index 3da4c9c271e..dcbfdc12dc8 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/JobDetailAwareTrigger.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/JobDetailAwareTrigger.java @@ -1,12 +1,12 @@ /* - * Copyright 2002-2005 the original author or authors. - * + * Copyright 2002-2011 the original author or authors. + * * 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. @@ -29,6 +29,9 @@ import org.quartz.JobDetail; * This involves the need to register the JobDetail object separately * with SchedulerFactoryBean. * + *

NOTE: As of Quartz 2.0, the recommended strategy is to define an + * entry of name "jobDetail" and type JobDetail in the trigger's JobDataMap. + * * @author Juergen Hoeller * @since 18.02.2004 * @see SchedulerFactoryBean#setTriggers @@ -38,6 +41,12 @@ import org.quartz.JobDetail; */ public interface JobDetailAwareTrigger { + /** + * Name of the key for the JobDetail value in the trigger's JobDataMap. + * This is an alternative to implementing the JobDetailAwareTrigger interface. + */ + String JOB_DETAIL_KEY = "jobDetail"; + /** * Return the JobDetail that this Trigger is associated with. * @return the associated JobDetail, or null if none diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/JobDetailBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/JobDetailBean.java index 8ff97295ed4..34c8395c93b 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/JobDetailBean.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/JobDetailBean.java @@ -36,8 +36,10 @@ import org.springframework.context.ApplicationContextAware; * and the Quartz default group ("DEFAULT") as job group if not specified. * *

NOTE: This convenience subclass does not work against Quartz 2.0. - * Use Quartz 2.0's native JobDetailImpl class or the new - * Quartz 2.0 builder API instead. + * Use Quartz 2.0's native JobDetailImpl class or the new Quartz 2.0 + * builder API instead. Alternatively, switch to Spring's {@link JobDetailFactoryBean} + * which largely is a drop-in replacement for this class and its properties and + * consistently works against Quartz 1.x as well as Quartz 2.0/2.1. * * @author Juergen Hoeller * @since 18.02.2004 diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java new file mode 100644 index 00000000000..d779c102064 --- /dev/null +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/JobDetailFactoryBean.java @@ -0,0 +1,208 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * 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. + */ + +package org.springframework.scheduling.quartz; + +import java.util.Map; + +import org.quartz.JobDataMap; +import org.quartz.JobDetail; +import org.quartz.Scheduler; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * A Spring {@link FactoryBean} for creating a Quartz {@link org.quartz.JobDetail} + * instance, supporting bean-style usage for JobDetail configuration. + * + *

JobDetail(Impl) itself is already a JavaBean but lacks + * sensible defaults. This class uses the Spring bean name as job name, + * and the Quartz default group ("DEFAULT") as job group if not specified. + * + *

NOTE: This FactoryBean works against both Quartz 1.x and Quartz 2.0/2.1, + * in contrast to the older {@link JobDetailBean} class. + * + * @author Juergen Hoeller + * @since 3.1 + * @see #setName + * @see #setGroup + * @see org.springframework.beans.factory.BeanNameAware + * @see org.quartz.Scheduler#DEFAULT_GROUP + */ +public class JobDetailFactoryBean + implements FactoryBean, BeanNameAware, ApplicationContextAware, InitializingBean { + + private String name; + + private String group; + + private Class jobClass; + + private JobDataMap jobDataMap = new JobDataMap(); + + private String beanName; + + private ApplicationContext applicationContext; + + private String applicationContextJobDataKey; + + private JobDetail jobDetail; + + + /** + * Specify the job's name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Specify the job's group. + */ + public void setGroup(String group) { + this.group = group; + } + + /** + * Specify the job's implementation class. + */ + public void setJobClass(Class jobClass) { + this.jobClass = jobClass; + } + + /** + * Set the job's JobDataMap. + * @see #setJobDataAsMap + */ + public void setJobDataMap(JobDataMap jobDataMap) { + this.jobDataMap = jobDataMap; + } + + /** + * Return the job's JobDataMap. + */ + public JobDataMap getJobDataMap() { + return this.jobDataMap; + } + + /** + * Register objects in the JobDataMap via a given Map. + *

These objects will be available to this Job only, + * in contrast to objects in the SchedulerContext. + *

Note: When using persistent Jobs whose JobDetail will be kept in the + * database, do not put Spring-managed beans or an ApplicationContext + * reference into the JobDataMap but rather into the SchedulerContext. + * @param jobDataAsMap Map with String keys and any objects as values + * (for example Spring-managed beans) + * @see org.springframework.scheduling.quartz.SchedulerFactoryBean#setSchedulerContextAsMap + */ + public void setJobDataAsMap(Map jobDataAsMap) { + getJobDataMap().putAll(jobDataAsMap); + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /** + * Set the key of an ApplicationContext reference to expose in the JobDataMap, + * for example "applicationContext". Default is none. + * Only applicable when running in a Spring ApplicationContext. + *

In case of a QuartzJobBean, the reference will be applied to the Job + * instance as bean property. An "applicationContext" attribute will correspond + * to a "setApplicationContext" method in that scenario. + *

Note that BeanFactory callback interfaces like ApplicationContextAware + * are not automatically applied to Quartz Job instances, because Quartz + * itself is responsible for the lifecycle of its Jobs. + *

Note: When using persistent job stores where JobDetail contents will + * be kept in the database, do not put an ApplicationContext reference into + * the JobDataMap but rather into the SchedulerContext. + * @see org.springframework.scheduling.quartz.SchedulerFactoryBean#setApplicationContextSchedulerContextKey + * @see org.springframework.context.ApplicationContext + */ + public void setApplicationContextJobDataKey(String applicationContextJobDataKey) { + this.applicationContextJobDataKey = applicationContextJobDataKey; + } + + + @SuppressWarnings("unchecked") + public void afterPropertiesSet() { + if (this.name == null) { + this.name = this.beanName; + } + if (this.group == null) { + this.group = Scheduler.DEFAULT_GROUP; + } + if (this.applicationContextJobDataKey != null) { + if (this.applicationContext == null) { + throw new IllegalStateException( + "JobDetailBean needs to be set up in an ApplicationContext " + + "to be able to handle an 'applicationContextJobDataKey'"); + } + getJobDataMap().put(this.applicationContextJobDataKey, this.applicationContext); + } + + /* + JobDetailImpl jdi = new JobDetailImpl(); + jdi.setName(this.name); + jdi.setGroup(this.group); + jdi.setJobClass(this.jobClass); + jdi.setJobDataMap(this.jobDataMap); + this.jobDetail = jdi; + */ + + Class jobDetailClass; + try { + jobDetailClass = getClass().getClassLoader().loadClass("org.quartz.impl.JobDetailImpl"); + } + catch (ClassNotFoundException ex) { + jobDetailClass = JobDetail.class; + } + BeanWrapper bw = new BeanWrapperImpl(jobDetailClass); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("name", this.name); + pvs.add("group", this.group); + pvs.add("jobClass", this.jobClass); + pvs.add("jobDataMap", this.jobDataMap); + bw.setPropertyValues(pvs); + this.jobDetail = (JobDetail) bw.getWrappedInstance(); + } + + + public JobDetail getObject() { + return this.jobDetail; + } + + public Class getObjectType() { + return JobDetail.class; + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java index d5b77b25707..2333e7d57c6 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/MethodInvokingJobDetailFactoryBean.java @@ -65,7 +65,7 @@ import org.springframework.util.MethodInvoker; * You need to implement your own Quartz Job as a thin wrapper for each case * where you want a persistent job to delegate to a specific service method. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. * * @author Juergen Hoeller * @author Alef Arendsen diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java index d10468aaf95..a28111c0042 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java @@ -51,7 +51,7 @@ import org.springframework.util.ReflectionUtils; *

For concrete usage, check out the {@link SchedulerFactoryBean} and * {@link SchedulerAccessorBean} classes. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. * * @author Juergen Hoeller * @since 2.5.6 @@ -359,8 +359,8 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware { boolean triggerExists = triggerExists(trigger); if (!triggerExists || this.overwriteExistingJobs) { // Check if the Trigger is aware of an associated JobDetail. - if (trigger instanceof JobDetailAwareTrigger) { - JobDetail jobDetail = ((JobDetailAwareTrigger) trigger).getJobDetail(); + JobDetail jobDetail = findJobDetail(trigger); + if (jobDetail != null) { // Automatically register the JobDetail too. if (!this.jobDetails.contains(jobDetail) && addJobToScheduler(jobDetail)) { this.jobDetails.add(jobDetail); @@ -390,6 +390,21 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware { } } + private JobDetail findJobDetail(Trigger trigger) { + if (trigger instanceof JobDetailAwareTrigger) { + return ((JobDetailAwareTrigger) trigger).getJobDetail(); + } + else { + try { + Map jobDataMap = (Map) ReflectionUtils.invokeMethod(Trigger.class.getMethod("getJobDataMap"), trigger); + return (JobDetail) jobDataMap.get(JobDetailAwareTrigger.JOB_DETAIL_KEY); + } + catch (NoSuchMethodException ex) { + throw new IllegalStateException("Inconsistent Quartz API: " + ex); + } + } + } + // Reflectively adapting to differences between Quartz 1.x and Quartz 2.0... private boolean jobDetailExists(JobDetail jobDetail) throws SchedulerException { diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessorBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessorBean.java index 44d7f18ca7c..d02689462da 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessorBean.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessorBean.java @@ -29,7 +29,7 @@ import org.springframework.beans.factory.ListableBeanFactory; * Spring bean-style class for accessing a Quartz Scheduler, i.e. for registering jobs, * triggers and listeners on a given {@link org.quartz.Scheduler} instance. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. * * @author Juergen Hoeller * @since 2.5.6 diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java index 5dde075eda9..966ad09b74d 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java @@ -74,7 +74,7 @@ import org.springframework.util.CollectionUtils; * automatically apply to Scheduler operations performed within those scopes. * Alternatively, you may add transactional advice for the Scheduler itself. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. * * @author Juergen Hoeller * @since 18.02.2004 diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerBean.java index acda87ddb9e..ca9d44c6586 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerBean.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerBean.java @@ -32,7 +32,7 @@ import org.springframework.core.Constants; * Convenience subclass of Quartz's {@link org.quartz.SimpleTrigger} class, * making bean-style usage easier. * - *

SimpleTrigger itself is already a JavaBean but lacks sensible defaults. + *

SimpleTrigger itself is already a JavaBean but lacks sensible defaults. * This class uses the Spring bean name as job name, the Quartz default group * ("DEFAULT") as job group, the current time as start time, and indefinite * repetition, if not specified. @@ -43,8 +43,10 @@ import org.springframework.core.Constants; * instead of registering the JobDetail separately. * *

NOTE: This convenience subclass does not work against Quartz 2.0. - * Use Quartz 2.0's native SimpleTriggerImpl class or the new - * Quartz 2.0 builder API instead. + * Use Quartz 2.0's native JobDetailImpl class or the new Quartz 2.0 + * builder API instead. Alternatively, switch to Spring's {@link SimpleTriggerFactoryBean} + * which largely is a drop-in replacement for this class and its properties and + * consistently works against Quartz 1.x as well as Quartz 2.0/2.1. * * @author Juergen Hoeller * @since 18.02.2004 @@ -84,7 +86,7 @@ public class SimpleTriggerBean extends SimpleTrigger * (for example Spring-managed beans) * @see JobDetailBean#setJobDataAsMap */ - public void setJobDataAsMap(Map jobDataAsMap) { + public void setJobDataAsMap(Map jobDataAsMap) { getJobDataMap().putAll(jobDataAsMap); } @@ -118,14 +120,12 @@ public class SimpleTriggerBean extends SimpleTrigger } /** - * Set the delay before starting the job for the first time. - * The given number of milliseconds will be added to the current - * time to calculate the start time. Default is 0. - *

This delay will just be applied if no custom start time was - * specified. However, in typical usage within a Spring context, - * the start time will be the container startup time anyway. - * Specifying a relative delay is appropriate in that case. - * @see #setStartTime + * Set the start delay in milliseconds. + *

The start delay is added to the current system time (when the bean starts) + * to control the {@link #setStartTime start time} of the trigger. + *

If the start delay is non-zero, it will always + * take precedence over start time. + * @param startDelay the start delay, in milliseconds */ public void setStartDelay(long startDelay) { this.startDelay = startDelay; diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBean.java new file mode 100644 index 00000000000..c26b0d00424 --- /dev/null +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SimpleTriggerFactoryBean.java @@ -0,0 +1,272 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * 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. + */ + +package org.springframework.scheduling.quartz; + +import java.lang.reflect.Method; +import java.text.ParseException; +import java.util.Date; +import java.util.Map; + +import org.quartz.JobDataMap; +import org.quartz.JobDetail; +import org.quartz.Scheduler; +import org.quartz.SimpleTrigger; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.Constants; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * A Spring {@link FactoryBean} for creating a Quartz {@link org.quartz.SimpleTrigger} + * instance, supporting bean-style usage for trigger configuration. + * + *

SimpleTrigger(Impl) itself is already a JavaBean but lacks sensible defaults. + * This class uses the Spring bean name as job name, the Quartz default group ("DEFAULT") + * as job group, the current time as start time, and indefinite repetition, if not specified. + * + *

This class will also register the trigger with the job name and group of + * a given {@link org.quartz.JobDetail}. This allows {@link SchedulerFactoryBean} + * to automatically register a trigger for the corresponding JobDetail, + * instead of registering the JobDetail separately. + * + *

NOTE: This FactoryBean works against both Quartz 1.x and Quartz 2.0/2.1, + * in contrast to the older {@link SimpleTriggerBean} class. + * + * @author Juergen Hoeller + * @since 3.1 + * @see #setName + * @see #setGroup + * @see #setStartTime + * @see #setJobName + * @see #setJobGroup + * @see #setJobDetail + * @see org.springframework.scheduling.quartz.SchedulerFactoryBean#setTriggers + * @see org.springframework.scheduling.quartz.SchedulerFactoryBean#setJobDetails + * @see org.springframework.scheduling.quartz.CronTriggerBean + */ +public class SimpleTriggerFactoryBean implements FactoryBean, BeanNameAware, InitializingBean { + + /** Constants for the SimpleTrigger class */ + private static final Constants constants = new Constants(SimpleTrigger.class); + + + private String name; + + private String group; + + private JobDetail jobDetail; + + private JobDataMap jobDataMap = new JobDataMap(); + + private Date startTime; + + private long startDelay; + + private long repeatInterval; + + private int priority; + + private int misfireInstruction; + + private String beanName; + + private SimpleTrigger simpleTrigger; + + + /** + * Specify the trigger's name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Specify the trigger's group. + */ + public void setGroup(String group) { + this.group = group; + } + + /** + * Set the JobDetail that this trigger should be associated with. + */ + public void setJobDetail(JobDetail jobDetail) { + this.jobDetail = jobDetail; + } + + /** + * Set the trigger's JobDataMap. + * @see #setJobDataAsMap + */ + public void setJobDataMap(JobDataMap jobDataMap) { + this.jobDataMap = jobDataMap; + } + + /** + * Return the trigger's JobDataMap. + */ + public JobDataMap getJobDataMap() { + return this.jobDataMap; + } + + /** + * Register objects in the JobDataMap via a given Map. + *

These objects will be available to this Trigger only, + * in contrast to objects in the JobDetail's data map. + * @param jobDataAsMap Map with String keys and any objects as values + * (for example Spring-managed beans) + * @see org.springframework.scheduling.quartz.JobDetailBean#setJobDataAsMap + */ + public void setJobDataAsMap(Map jobDataAsMap) { + this.jobDataMap.putAll(jobDataAsMap); + } + + /** + * Set the start delay in milliseconds. + *

The start delay is added to the current system time (when the bean starts) + * to control the start time of the trigger. + * @param startDelay the start delay, in milliseconds + */ + public void setStartDelay(long startDelay) { + Assert.state(startDelay >= 0, "Start delay cannot be negative."); + this.startDelay = startDelay; + } + + /** + * Specify the interval between execution times of this trigger. + */ + public void setRepeatInterval(long repeatInterval) { + this.repeatInterval = repeatInterval; + } + + /** + * Specify the priority of this trigger. + */ + public void setPriority(int priority) { + this.priority = priority; + } + + /** + * Specify a misfire instruction for this trigger. + */ + public void setMisfireInstruction(int misfireInstruction) { + this.misfireInstruction = misfireInstruction; + } + + /** + * Set the misfire instruction via the name of the corresponding + * constant in the {@link org.quartz.SimpleTrigger} class. + * Default is MISFIRE_INSTRUCTION_SMART_POLICY. + * @see org.quartz.SimpleTrigger#MISFIRE_INSTRUCTION_FIRE_NOW + * @see org.quartz.SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT + * @see org.quartz.SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT + * @see org.quartz.SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT + * @see org.quartz.SimpleTrigger#MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT + * @see org.quartz.Trigger#MISFIRE_INSTRUCTION_SMART_POLICY + */ + public void setMisfireInstructionName(String constantName) { + this.misfireInstruction = constants.asNumber(constantName).intValue(); + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + + public void afterPropertiesSet() throws ParseException { + if (this.name == null) { + this.name = this.beanName; + } + if (this.group == null) { + this.group = Scheduler.DEFAULT_GROUP; + } + if (this.jobDetail != null) { + this.jobDataMap.put(JobDetailAwareTrigger.JOB_DETAIL_KEY, this.jobDetail); + } + if (this.startDelay > 0) { + this.startTime = new Date(System.currentTimeMillis() + this.startDelay); + } + else if (this.startTime == null) { + this.startTime = new Date(); + } + + /* + SimpleTriggerImpl sti = new SimpleTriggerImpl(); + sti.setName(this.name); + sti.setGroup(this.group); + sti.setJobKey(this.jobDetail.getKey()); + sti.setJobDataMap(this.jobDataMap); + sti.setStartTime(this.startTime); + sti.setRepeatInterval(this.repeatInterval); + sti.setPriority(this.priority); + sti.setMisfireInstruction(this.misfireInstruction); + this.simpleTrigger = sti; + */ + + Class simpleTriggerClass; + Method jobKeyMethod; + try { + simpleTriggerClass = getClass().getClassLoader().loadClass("org.quartz.impl.triggers.SimpleTriggerImpl"); + jobKeyMethod = JobDetail.class.getMethod("getKey"); + } + catch (ClassNotFoundException ex) { + simpleTriggerClass = SimpleTrigger.class; + jobKeyMethod = null; + } + catch (NoSuchMethodException ex) { + throw new IllegalStateException("Incompatible Quartz version"); + } + BeanWrapper bw = new BeanWrapperImpl(simpleTriggerClass); + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("name", this.name); + pvs.add("group", this.group); + if (jobKeyMethod != null) { + pvs.add("jobKey", ReflectionUtils.invokeMethod(jobKeyMethod, this.jobDetail)); + } + else { + pvs.add("jobName", this.jobDetail.getName()); + pvs.add("jobGroup", this.jobDetail.getGroup()); + } + pvs.add("jobDataMap", this.jobDataMap); + pvs.add("startTime", this.startTime); + pvs.add("repeatInterval", this.repeatInterval); + pvs.add("repeatCount", -1); + pvs.add("priority", this.priority); + pvs.add("misfireInstruction", this.misfireInstruction); + bw.setPropertyValues(pvs); + this.simpleTrigger = (SimpleTrigger) bw.getWrappedInstance(); + } + + + public SimpleTrigger getObject() { + return this.simpleTrigger; + } + + public Class getObjectType() { + return SimpleTrigger.class; + } + + public boolean isSingleton() { + return true; + } +} diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java index dfcd158d9f9..5e555e2d04d 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java @@ -37,7 +37,7 @@ import org.springframework.util.ReflectionUtils; * as bean property values. If no matching bean property is found, the entry * is by default simply ignored. This is analogous to QuartzJobBean's behavior. * - *

Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1. + *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. * * @author Juergen Hoeller * @since 2.0