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}