提交 69c9abf2 编写于 作者: J Juergen Hoeller

updated Quartz support package for full Quartz 2.0 support (SPR-8275)

上级 cc7c64a3
......@@ -16,17 +16,21 @@
package org.springframework.scheduling.quartz;
import java.lang.reflect.Method;
import org.quartz.Job;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.spi.JobFactory;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.util.ReflectionUtils;
/**
* JobFactory implementation that supports {@link java.lang.Runnable}
* objects as well as standard Quartz {@link org.quartz.Job} instances.
*
* <p>Compatible with Quartz 1.x as well as Quartz 2.0, as of Spring 3.1.
* <p>Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1.
*
* @author Juergen Hoeller
* @since 2.0
......@@ -65,7 +69,12 @@ public class AdaptableJobFactory implements JobFactory {
* @throws Exception if job instantiation failed
*/
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
return bundle.getJobDetail().getJobClass().newInstance();
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
Method getJobDetail = bundle.getClass().getMethod("getJobDetail");
Object jobDetail = ReflectionUtils.invokeMethod(getJobDetail, bundle);
Method getJobClass = jobDetail.getClass().getMethod("getJobClass");
Class jobClass = (Class) ReflectionUtils.invokeMethod(getJobClass, jobDetail);
return jobClass.newInstance();
}
/**
......
/*
* Copyright 2002-2009 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.
......@@ -43,10 +43,9 @@ import org.springframework.util.Assert;
* to automatically register a trigger for the corresponding JobDetail,
* instead of registering the JobDetail separately.
*
* <p><b>NOTE:</b> This convenience subclass does not work with trigger
* persistence in Quartz 1.6, due to a change in Quartz's trigger handling.
* Use Quartz 1.5 if you rely on trigger persistence based on this class,
* or the standard Quartz {@link org.quartz.CronTrigger} class instead.
* <p><b>NOTE: This convenience subclass does not work against Quartz 2.0.</b>
* Use Quartz 2.0's native <code>CronTriggerImpl</code> class or the new
* Quartz 2.0 builder API instead.
*
* @author Juergen Hoeller
* @since 18.02.2004
......
/*
* Copyright 2002-2009 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.
......@@ -35,6 +35,10 @@ import org.springframework.context.ApplicationContextAware;
* sensible defaults. This class uses the Spring bean name as job name,
* and the Quartz default group ("DEFAULT") as job group if not specified.
*
* <p><b>NOTE: This convenience subclass does not work against Quartz 2.0.</b>
* Use Quartz 2.0's native <code>JobDetailImpl</code> class or the new
* Quartz 2.0 builder API instead.
*
* @author Juergen Hoeller
* @since 18.02.2004
* @see #setName
......
/*
* Copyright 2002-2009 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.
......@@ -20,12 +20,16 @@ import java.lang.reflect.InvocationTargetException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.StatefulJob;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
......@@ -61,6 +65,8 @@ 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.
*
* <p>Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1.
*
* @author Juergen Hoeller
* @author Alef Arendsen
* @since 18.02.2004
......@@ -70,7 +76,19 @@ import org.springframework.util.MethodInvoker;
* @see #setConcurrent
*/
public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethodInvoker
implements FactoryBean<Object>, BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean {
implements FactoryBean<JobDetail>, BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean {
private static Class<?> jobDetailImplClass;
static {
try {
jobDetailImplClass = Class.forName("org.quartz.impl.JobDetailImpl");
}
catch (ClassNotFoundException ex) {
jobDetailImplClass = null;
}
}
private String name;
......@@ -174,14 +192,31 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
Class jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class);
// Build JobDetail instance.
if (jobDetailImplClass != null) {
// Using Quartz 2.0 JobDetailImpl class...
Object jobDetail = BeanUtils.instantiate(jobDetailImplClass);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(jobDetail);
bw.setPropertyValue("name", name);
bw.setPropertyValue("group", this.group);
bw.setPropertyValue("jobClass", jobClass);
bw.setPropertyValue("durability", true);
((JobDataMap) bw.getPropertyValue("jobDataMap")).put("methodInvoker", this);
}
else {
// Using Quartz 1.x JobDetail class...
this.jobDetail = new JobDetail(name, this.group, jobClass);
this.jobDetail.getJobDataMap().put("methodInvoker", this);
this.jobDetail.setVolatility(true);
this.jobDetail.setDurability(true);
this.jobDetail.getJobDataMap().put("methodInvoker", this);
}
// Register job listener names.
if (this.jobListenerNames != null) {
for (String jobListenerName : this.jobListenerNames) {
if (jobDetailImplClass != null) {
throw new IllegalStateException("Non-global JobListeners not supported on Quartz 2 - " +
"manually register a Matcher against the Quartz ListenerManager instead");
}
this.jobDetail.addJobListener(jobListenerName);
}
}
......@@ -225,12 +260,12 @@ public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethod
}
public Object getObject() {
public JobDetail getObject() {
return this.jobDetail;
}
public Class<?> getObjectType() {
return JobDetail.class;
public Class<? extends JobDetail> getObjectType() {
return (this.jobDetail != null ? this.jobDetail.getClass() : JobDetail.class);
}
public boolean isSingleton() {
......
......@@ -42,6 +42,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ReflectionUtils;
/**
* Common base class for accessing a Quartz Scheduler, i.e. for registering jobs,
......@@ -50,13 +51,30 @@ import org.springframework.transaction.support.DefaultTransactionDefinition;
* <p>For concrete usage, check out the {@link SchedulerFactoryBean} and
* {@link SchedulerAccessorBean} classes.
*
* <p>Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1.
*
* @author Juergen Hoeller
* @since 2.5.6
*/
public abstract class SchedulerAccessor implements ResourceLoaderAware {
protected final Log logger = LogFactory.getLog(getClass());
private static Class<?> jobKeyClass;
private static Class<?> triggerKeyClass;
static {
try {
jobKeyClass = Class.forName("org.quartz.JobKey");
triggerKeyClass = Class.forName("org.quartz.TriggerKey");
}
catch (ClassNotFoundException ex) {
jobKeyClass = null;
triggerKeyClass = null;
}
}
protected final Log logger = LogFactory.getLog(getClass());
private boolean overwriteExistingJobs = false;
......@@ -68,7 +86,6 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
private List<Trigger> triggers;
private SchedulerListener[] schedulerListeners;
private JobListener[] globalJobListeners;
......@@ -79,7 +96,6 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
private TriggerListener[] triggerListeners;
private PlatformTransactionManager transactionManager;
protected ResourceLoader resourceLoader;
......@@ -322,8 +338,7 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
* @see #setOverwriteExistingJobs
*/
private boolean addJobToScheduler(JobDetail jobDetail) throws SchedulerException {
if (this.overwriteExistingJobs ||
getScheduler().getJobDetail(jobDetail.getName(), jobDetail.getGroup()) == null) {
if (this.overwriteExistingJobs || !jobDetailExists(jobDetail)) {
getScheduler().addJob(jobDetail, true);
return true;
}
......@@ -341,7 +356,7 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
* @see #setOverwriteExistingJobs
*/
private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException {
boolean triggerExists = (getScheduler().getTrigger(trigger.getName(), trigger.getGroup()) != null);
boolean triggerExists = triggerExists(trigger);
if (!triggerExists || this.overwriteExistingJobs) {
// Check if the Trigger is aware of an associated JobDetail.
if (trigger instanceof JobDetailAwareTrigger) {
......@@ -361,12 +376,12 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
ex.getMessage() + " - can safely be ignored");
}
if (this.overwriteExistingJobs) {
getScheduler().rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
rescheduleJob(trigger);
}
}
}
else {
getScheduler().rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
rescheduleJob(trigger);
}
return true;
}
......@@ -376,36 +391,118 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
}
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
private boolean jobDetailExists(JobDetail jobDetail) throws SchedulerException {
if (jobKeyClass != null) {
try {
Method getJobDetail = Scheduler.class.getMethod("getJobDetail", jobKeyClass);
Object key = ReflectionUtils.invokeMethod(JobDetail.class.getMethod("getKey"), jobDetail);
return (ReflectionUtils.invokeMethod(getJobDetail, getScheduler(), key) != null);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("Inconsistent Quartz 2.0 API: " + ex);
}
}
else {
return (getScheduler().getJobDetail(jobDetail.getName(), jobDetail.getGroup()) != null);
}
}
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
private boolean triggerExists(Trigger trigger) throws SchedulerException {
if (triggerKeyClass != null) {
try {
Method getTrigger = Scheduler.class.getMethod("getTrigger", triggerKeyClass);
Object key = ReflectionUtils.invokeMethod(Trigger.class.getMethod("getKey"), trigger);
return (ReflectionUtils.invokeMethod(getTrigger, getScheduler(), key) != null);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("Inconsistent Quartz 2.0 API: " + ex);
}
}
else {
return (getScheduler().getTrigger(trigger.getName(), trigger.getGroup()) != null);
}
}
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
private void rescheduleJob(Trigger trigger) throws SchedulerException {
if (triggerKeyClass != null) {
try {
Method rescheduleJob = Scheduler.class.getMethod("rescheduleJob", triggerKeyClass, Trigger.class);
Object key = ReflectionUtils.invokeMethod(Trigger.class.getMethod("getKey"), trigger);
ReflectionUtils.invokeMethod(rescheduleJob, getScheduler(), key, trigger);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("Inconsistent Quartz 2.0 API: " + ex);
}
}
else {
getScheduler().rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
}
}
/**
* Register all specified listeners with the Scheduler.
*/
protected void registerListeners() throws SchedulerException {
Object target;
boolean quartz2;
try {
Method getListenerManager = Scheduler.class.getMethod("getListenerManager");
target = ReflectionUtils.invokeMethod(getListenerManager, getScheduler());
quartz2 = true;
}
catch (NoSuchMethodException ex) {
target = getScheduler();
quartz2 = false;
}
try {
if (this.schedulerListeners != null) {
Method addSchedulerListener = target.getClass().getMethod("addSchedulerListener", SchedulerListener.class);
for (SchedulerListener listener : this.schedulerListeners) {
getScheduler().addSchedulerListener(listener);
ReflectionUtils.invokeMethod(addSchedulerListener, target, listener);
}
}
if (this.globalJobListeners != null) {
Method addJobListener = target.getClass().getMethod(
(quartz2 ? "addJobListener" : "addGlobalJobListener"), JobListener.class);
for (JobListener listener : this.globalJobListeners) {
getScheduler().addGlobalJobListener(listener);
ReflectionUtils.invokeMethod(addJobListener, target, listener);
}
}
if (this.jobListeners != null) {
for (JobListener listener : this.jobListeners) {
if (quartz2) {
throw new IllegalStateException("Non-global JobListeners not supported on Quartz 2 - " +
"manually register a Matcher against the Quartz ListenerManager instead");
}
getScheduler().addJobListener(listener);
}
}
if (this.globalTriggerListeners != null) {
Method addTriggerListener = target.getClass().getMethod(
(quartz2 ? "addTriggerListener" : "addGlobalTriggerListener"), TriggerListener.class);
for (TriggerListener listener : this.globalTriggerListeners) {
getScheduler().addGlobalTriggerListener(listener);
ReflectionUtils.invokeMethod(addTriggerListener, target, listener);
}
}
if (this.triggerListeners != null) {
for (TriggerListener listener : this.triggerListeners) {
if (quartz2) {
throw new IllegalStateException("Non-global TriggerListeners not supported on Quartz 2 - " +
"manually register a Matcher against the Quartz ListenerManager instead");
}
getScheduler().addTriggerListener(listener);
}
}
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("Expected Quartz API not present: " + ex);
}
}
/**
......
/*
* Copyright 2002-2008 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.
......@@ -29,6 +29,8 @@ 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.
*
* <p>Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1.
*
* @author Juergen Hoeller
* @since 2.5.6
* @see #setScheduler
......
......@@ -74,9 +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.
*
* <p><b>Note:</b> This version of Spring's SchedulerFactoryBean supports Quartz 1.x,
* more specifically Quartz 1.5 or higher. The "jobSchedulingDataLocation" feature
* requires Quartz 1.6.1 or higher (as of Spring 2.5.5).
* <p>Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1.
*
* @author Juergen Hoeller
* @since 18.02.2004
......
/*
* Copyright 2002-2009 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.
......@@ -42,10 +42,9 @@ import org.springframework.core.Constants;
* to automatically register a trigger for the corresponding JobDetail,
* instead of registering the JobDetail separately.
*
* <p><b>NOTE:</b> This convenience subclass does not work with trigger
* persistence in Quartz 1.6, due to a change in Quartz's trigger handling.
* Use Quartz 1.5 if you rely on trigger persistence based on this class,
* or the standard Quartz {@link org.quartz.SimpleTrigger} class instead.
* <p><b>NOTE: This convenience subclass does not work against Quartz 2.0.</b>
* Use Quartz 2.0's native <code>SimpleTriggerImpl</code> class or the new
* Quartz 2.0 builder API instead.
*
* @author Juergen Hoeller
* @since 18.02.2004
......
/*
* Copyright 2002-2009 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.
......@@ -16,12 +16,16 @@
package org.springframework.scheduling.quartz;
import java.lang.reflect.Method;
import org.quartz.JobDataMap;
import org.quartz.SchedulerContext;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.util.ReflectionUtils;
/**
* Subclass of {@link AdaptableJobFactory} that also supports Spring-style
......@@ -33,6 +37,8 @@ import org.springframework.beans.PropertyAccessorFactory;
* 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.
*
* <p>Compatible with Quartz 1.5+ as well as Quartz 2.0, as of Spring 3.1.
*
* @author Juergen Hoeller
* @since 2.0
* @see SchedulerFactoryBean#setJobFactory
......@@ -68,15 +74,15 @@ public class SpringBeanJobFactory extends AdaptableJobFactory implements Schedul
*/
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object job = bundle.getJobDetail().getJobClass().newInstance();
Object job = super.createJobInstance(bundle);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
if (isEligibleForPropertyPopulation(bw.getWrappedInstance())) {
MutablePropertyValues pvs = new MutablePropertyValues();
if (this.schedulerContext != null) {
pvs.addPropertyValues(this.schedulerContext);
}
pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
pvs.addPropertyValues(getJobDetailDataMap(bundle));
pvs.addPropertyValues(getTriggerDataMap(bundle));
if (this.ignoredUnknownProperties != null) {
for (String propName : this.ignoredUnknownProperties) {
if (pvs.contains(propName) && !bw.isWritableProperty(propName)) {
......@@ -104,4 +110,20 @@ public class SpringBeanJobFactory extends AdaptableJobFactory implements Schedul
return (!(jobObject instanceof QuartzJobBean));
}
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
private JobDataMap getJobDetailDataMap(TriggerFiredBundle bundle) throws Exception {
Method getJobDetail = bundle.getClass().getMethod("getJobDetail");
Object jobDetail = ReflectionUtils.invokeMethod(getJobDetail, bundle);
Method getJobDataMap = jobDetail.getClass().getMethod("getJobDataMap");
return (JobDataMap) ReflectionUtils.invokeMethod(getJobDataMap, jobDetail);
}
// Reflectively adapting to differences between Quartz 1.x and Quartz 2.0...
private JobDataMap getTriggerDataMap(TriggerFiredBundle bundle) throws Exception {
Method getTrigger = bundle.getClass().getMethod("getTrigger");
Object trigger = ReflectionUtils.invokeMethod(getTrigger, bundle);
Method getJobDataMap = trigger.getClass().getMethod("getJobDataMap");
return (JobDataMap) ReflectionUtils.invokeMethod(getJobDataMap, trigger);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册