001/* 002 * Copyright 2002-2014 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.scheduling.quartz; 018 019import java.lang.reflect.InvocationTargetException; 020 021import org.apache.commons.logging.Log; 022import org.apache.commons.logging.LogFactory; 023import org.quartz.DisallowConcurrentExecution; 024import org.quartz.JobDetail; 025import org.quartz.JobExecutionContext; 026import org.quartz.JobExecutionException; 027import org.quartz.PersistJobDataAfterExecution; 028import org.quartz.Scheduler; 029import org.quartz.impl.JobDetailImpl; 030 031import org.springframework.beans.factory.BeanClassLoaderAware; 032import org.springframework.beans.factory.BeanFactory; 033import org.springframework.beans.factory.BeanFactoryAware; 034import org.springframework.beans.factory.BeanNameAware; 035import org.springframework.beans.factory.FactoryBean; 036import org.springframework.beans.factory.InitializingBean; 037import org.springframework.beans.support.ArgumentConvertingMethodInvoker; 038import org.springframework.util.Assert; 039import org.springframework.util.ClassUtils; 040import org.springframework.util.MethodInvoker; 041 042/** 043 * {@link org.springframework.beans.factory.FactoryBean} that exposes a 044 * {@link org.quartz.JobDetail} object which delegates job execution to a 045 * specified (static or non-static) method. Avoids the need for implementing 046 * a one-line Quartz Job that just invokes an existing service method on a 047 * Spring-managed target bean. 048 * 049 * <p>Inherits common configuration properties from the {@link MethodInvoker} 050 * base class, such as {@link #setTargetObject "targetObject"} and 051 * {@link #setTargetMethod "targetMethod"}, adding support for lookup of the target 052 * bean by name through the {@link #setTargetBeanName "targetBeanName"} property 053 * (as alternative to specifying a "targetObject" directly, allowing for 054 * non-singleton target objects). 055 * 056 * <p>Supports both concurrently running jobs and non-currently running 057 * jobs through the "concurrent" property. Jobs created by this 058 * MethodInvokingJobDetailFactoryBean are by default volatile and durable 059 * (according to Quartz terminology). 060 * 061 * <p><b>NOTE: JobDetails created via this FactoryBean are <i>not</i> 062 * serializable and thus not suitable for persistent job stores.</b> 063 * You need to implement your own Quartz Job as a thin wrapper for each case 064 * where you want a persistent job to delegate to a specific service method. 065 * 066 * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1. 067 * 068 * @author Juergen Hoeller 069 * @author Alef Arendsen 070 * @since 18.02.2004 071 * @see #setTargetBeanName 072 * @see #setTargetObject 073 * @see #setTargetMethod 074 * @see #setConcurrent 075 */ 076public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethodInvoker 077 implements FactoryBean<JobDetail>, BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean { 078 079 private String name; 080 081 private String group = Scheduler.DEFAULT_GROUP; 082 083 private boolean concurrent = true; 084 085 private String targetBeanName; 086 087 private String beanName; 088 089 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 090 091 private BeanFactory beanFactory; 092 093 private JobDetail jobDetail; 094 095 096 /** 097 * Set the name of the job. 098 * <p>Default is the bean name of this FactoryBean. 099 */ 100 public void setName(String name) { 101 this.name = name; 102 } 103 104 /** 105 * Set the group of the job. 106 * <p>Default is the default group of the Scheduler. 107 * @see org.quartz.Scheduler#DEFAULT_GROUP 108 */ 109 public void setGroup(String group) { 110 this.group = group; 111 } 112 113 /** 114 * Specify whether or not multiple jobs should be run in a concurrent fashion. 115 * The behavior when one does not want concurrent jobs to be executed is 116 * realized through adding the {@code @PersistJobDataAfterExecution} and 117 * {@code @DisallowConcurrentExecution} markers. 118 * More information on stateful versus stateless jobs can be found 119 * <a href="https://www.quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/tutorial-lesson-03">here</a>. 120 * <p>The default setting is to run jobs concurrently. 121 */ 122 public void setConcurrent(boolean concurrent) { 123 this.concurrent = concurrent; 124 } 125 126 /** 127 * Set the name of the target bean in the Spring BeanFactory. 128 * <p>This is an alternative to specifying {@link #setTargetObject "targetObject"}, 129 * allowing for non-singleton beans to be invoked. Note that specified 130 * "targetObject" and {@link #setTargetClass "targetClass"} values will 131 * override the corresponding effect of this "targetBeanName" setting 132 * (i.e. statically pre-define the bean type or even the bean object). 133 */ 134 public void setTargetBeanName(String targetBeanName) { 135 this.targetBeanName = targetBeanName; 136 } 137 138 @Override 139 public void setBeanName(String beanName) { 140 this.beanName = beanName; 141 } 142 143 @Override 144 public void setBeanClassLoader(ClassLoader classLoader) { 145 this.beanClassLoader = classLoader; 146 } 147 148 @Override 149 public void setBeanFactory(BeanFactory beanFactory) { 150 this.beanFactory = beanFactory; 151 } 152 153 @Override 154 protected Class<?> resolveClassName(String className) throws ClassNotFoundException { 155 return ClassUtils.forName(className, this.beanClassLoader); 156 } 157 158 159 @Override 160 @SuppressWarnings("unchecked") 161 public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException { 162 prepare(); 163 164 // Use specific name if given, else fall back to bean name. 165 String name = (this.name != null ? this.name : this.beanName); 166 167 // Consider the concurrent flag to choose between stateful and stateless job. 168 Class<?> jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class); 169 170 // Build JobDetail instance. 171 JobDetailImpl jdi = new JobDetailImpl(); 172 jdi.setName(name); 173 jdi.setGroup(this.group); 174 jdi.setJobClass((Class) jobClass); 175 jdi.setDurability(true); 176 jdi.getJobDataMap().put("methodInvoker", this); 177 this.jobDetail = jdi; 178 179 postProcessJobDetail(this.jobDetail); 180 } 181 182 /** 183 * Callback for post-processing the JobDetail to be exposed by this FactoryBean. 184 * <p>The default implementation is empty. Can be overridden in subclasses. 185 * @param jobDetail the JobDetail prepared by this FactoryBean 186 */ 187 protected void postProcessJobDetail(JobDetail jobDetail) { 188 } 189 190 191 /** 192 * Overridden to support the {@link #setTargetBeanName "targetBeanName"} feature. 193 */ 194 @Override 195 public Class<?> getTargetClass() { 196 Class<?> targetClass = super.getTargetClass(); 197 if (targetClass == null && this.targetBeanName != null) { 198 Assert.state(this.beanFactory != null, "BeanFactory must be set when using 'targetBeanName'"); 199 targetClass = this.beanFactory.getType(this.targetBeanName); 200 } 201 return targetClass; 202 } 203 204 /** 205 * Overridden to support the {@link #setTargetBeanName "targetBeanName"} feature. 206 */ 207 @Override 208 public Object getTargetObject() { 209 Object targetObject = super.getTargetObject(); 210 if (targetObject == null && this.targetBeanName != null) { 211 Assert.state(this.beanFactory != null, "BeanFactory must be set when using 'targetBeanName'"); 212 targetObject = this.beanFactory.getBean(this.targetBeanName); 213 } 214 return targetObject; 215 } 216 217 218 @Override 219 public JobDetail getObject() { 220 return this.jobDetail; 221 } 222 223 @Override 224 public Class<? extends JobDetail> getObjectType() { 225 return (this.jobDetail != null ? this.jobDetail.getClass() : JobDetail.class); 226 } 227 228 @Override 229 public boolean isSingleton() { 230 return true; 231 } 232 233 234 /** 235 * Quartz Job implementation that invokes a specified method. 236 * Automatically applied by MethodInvokingJobDetailFactoryBean. 237 */ 238 public static class MethodInvokingJob extends QuartzJobBean { 239 240 protected static final Log logger = LogFactory.getLog(MethodInvokingJob.class); 241 242 private MethodInvoker methodInvoker; 243 244 /** 245 * Set the MethodInvoker to use. 246 */ 247 public void setMethodInvoker(MethodInvoker methodInvoker) { 248 this.methodInvoker = methodInvoker; 249 } 250 251 /** 252 * Invoke the method via the MethodInvoker. 253 */ 254 @Override 255 protected void executeInternal(JobExecutionContext context) throws JobExecutionException { 256 try { 257 context.setResult(this.methodInvoker.invoke()); 258 } 259 catch (InvocationTargetException ex) { 260 if (ex.getTargetException() instanceof JobExecutionException) { 261 // -> JobExecutionException, to be logged at info level by Quartz 262 throw (JobExecutionException) ex.getTargetException(); 263 } 264 else { 265 // -> "unhandled exception", to be logged at error level by Quartz 266 throw new JobMethodInvocationFailedException(this.methodInvoker, ex.getTargetException()); 267 } 268 } 269 catch (Exception ex) { 270 // -> "unhandled exception", to be logged at error level by Quartz 271 throw new JobMethodInvocationFailedException(this.methodInvoker, ex); 272 } 273 } 274 } 275 276 277 /** 278 * Extension of the MethodInvokingJob, implementing the StatefulJob interface. 279 * Quartz checks whether or not jobs are stateful and if so, 280 * won't let jobs interfere with each other. 281 */ 282 @PersistJobDataAfterExecution 283 @DisallowConcurrentExecution 284 public static class StatefulMethodInvokingJob extends MethodInvokingJob { 285 286 // No implementation, just an addition of the tag interface StatefulJob 287 // in order to allow stateful method invoking jobs. 288 } 289 290}