001/*
002 * Copyright 2002-2018 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.text.ParseException;
020import java.util.Date;
021import java.util.Map;
022import java.util.TimeZone;
023
024import org.quartz.CronTrigger;
025import org.quartz.JobDataMap;
026import org.quartz.JobDetail;
027import org.quartz.Scheduler;
028import org.quartz.impl.triggers.CronTriggerImpl;
029
030import org.springframework.beans.factory.BeanNameAware;
031import org.springframework.beans.factory.FactoryBean;
032import org.springframework.beans.factory.InitializingBean;
033import org.springframework.core.Constants;
034import org.springframework.lang.Nullable;
035import org.springframework.util.Assert;
036
037/**
038 * A Spring {@link FactoryBean} for creating a Quartz {@link org.quartz.CronTrigger}
039 * instance, supporting bean-style usage for trigger configuration.
040 *
041 * <p>{@code CronTrigger(Impl)} itself is already a JavaBean but lacks sensible defaults.
042 * This class uses the Spring bean name as job name, the Quartz default group ("DEFAULT")
043 * as job group, the current time as start time, and indefinite repetition, if not specified.
044 *
045 * <p>This class will also register the trigger with the job name and group of
046 * a given {@link org.quartz.JobDetail}. This allows {@link SchedulerFactoryBean}
047 * to automatically register a trigger for the corresponding JobDetail,
048 * instead of registering the JobDetail separately.
049 *
050 * @author Juergen Hoeller
051 * @since 3.1
052 * @see #setName
053 * @see #setGroup
054 * @see #setStartDelay
055 * @see #setJobDetail
056 * @see SchedulerFactoryBean#setTriggers
057 * @see SchedulerFactoryBean#setJobDetails
058 */
059public class CronTriggerFactoryBean implements FactoryBean<CronTrigger>, BeanNameAware, InitializingBean {
060
061        /** Constants for the CronTrigger class. */
062        private static final Constants constants = new Constants(CronTrigger.class);
063
064
065        @Nullable
066        private String name;
067
068        @Nullable
069        private String group;
070
071        @Nullable
072        private JobDetail jobDetail;
073
074        private JobDataMap jobDataMap = new JobDataMap();
075
076        @Nullable
077        private Date startTime;
078
079        private long startDelay = 0;
080
081        @Nullable
082        private String cronExpression;
083
084        @Nullable
085        private TimeZone timeZone;
086
087        @Nullable
088        private String calendarName;
089
090        private int priority;
091
092        private int misfireInstruction;
093
094        @Nullable
095        private String description;
096
097        @Nullable
098        private String beanName;
099
100        @Nullable
101        private CronTrigger cronTrigger;
102
103
104        /**
105         * Specify the trigger's name.
106         */
107        public void setName(String name) {
108                this.name = name;
109        }
110
111        /**
112         * Specify the trigger's group.
113         */
114        public void setGroup(String group) {
115                this.group = group;
116        }
117
118        /**
119         * Set the JobDetail that this trigger should be associated with.
120         */
121        public void setJobDetail(JobDetail jobDetail) {
122                this.jobDetail = jobDetail;
123        }
124
125        /**
126         * Set the trigger's JobDataMap.
127         * @see #setJobDataAsMap
128         */
129        public void setJobDataMap(JobDataMap jobDataMap) {
130                this.jobDataMap = jobDataMap;
131        }
132
133        /**
134         * Return the trigger's JobDataMap.
135         */
136        public JobDataMap getJobDataMap() {
137                return this.jobDataMap;
138        }
139
140        /**
141         * Register objects in the JobDataMap via a given Map.
142         * <p>These objects will be available to this Trigger only,
143         * in contrast to objects in the JobDetail's data map.
144         * @param jobDataAsMap a Map with String keys and any objects as values
145         * (for example Spring-managed beans)
146         */
147        public void setJobDataAsMap(Map<String, ?> jobDataAsMap) {
148                this.jobDataMap.putAll(jobDataAsMap);
149        }
150
151        /**
152         * Set a specific start time for the trigger.
153         * <p>Note that a dynamically computed {@link #setStartDelay} specification
154         * overrides a static timestamp set here.
155         */
156        public void setStartTime(Date startTime) {
157                this.startTime = startTime;
158        }
159
160        /**
161         * Set the start delay in milliseconds.
162         * <p>The start delay is added to the current system time (when the bean starts)
163         * to control the start time of the trigger.
164         */
165        public void setStartDelay(long startDelay) {
166                Assert.isTrue(startDelay >= 0, "Start delay cannot be negative");
167                this.startDelay = startDelay;
168        }
169
170        /**
171         * Specify the cron expression for this trigger.
172         */
173        public void setCronExpression(String cronExpression) {
174                this.cronExpression = cronExpression;
175        }
176
177        /**
178         * Specify the time zone for this trigger's cron expression.
179         */
180        public void setTimeZone(TimeZone timeZone) {
181                this.timeZone = timeZone;
182        }
183
184        /**
185         * Associate a specific calendar with this cron trigger.
186         */
187        public void setCalendarName(String calendarName) {
188                this.calendarName = calendarName;
189        }
190
191        /**
192         * Specify the priority of this trigger.
193         */
194        public void setPriority(int priority) {
195                this.priority = priority;
196        }
197
198        /**
199         * Specify a misfire instruction for this trigger.
200         */
201        public void setMisfireInstruction(int misfireInstruction) {
202                this.misfireInstruction = misfireInstruction;
203        }
204
205        /**
206         * Set the misfire instruction via the name of the corresponding
207         * constant in the {@link org.quartz.CronTrigger} class.
208         * Default is {@code MISFIRE_INSTRUCTION_SMART_POLICY}.
209         * @see org.quartz.CronTrigger#MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
210         * @see org.quartz.CronTrigger#MISFIRE_INSTRUCTION_DO_NOTHING
211         * @see org.quartz.Trigger#MISFIRE_INSTRUCTION_SMART_POLICY
212         */
213        public void setMisfireInstructionName(String constantName) {
214                this.misfireInstruction = constants.asNumber(constantName).intValue();
215        }
216
217        /**
218         * Associate a textual description with this trigger.
219         */
220        public void setDescription(String description) {
221                this.description = description;
222        }
223
224        @Override
225        public void setBeanName(String beanName) {
226                this.beanName = beanName;
227        }
228
229
230        @Override
231        public void afterPropertiesSet() throws ParseException {
232                Assert.notNull(this.cronExpression, "Property 'cronExpression' is required");
233
234                if (this.name == null) {
235                        this.name = this.beanName;
236                }
237                if (this.group == null) {
238                        this.group = Scheduler.DEFAULT_GROUP;
239                }
240                if (this.jobDetail != null) {
241                        this.jobDataMap.put("jobDetail", this.jobDetail);
242                }
243                if (this.startDelay > 0 || this.startTime == null) {
244                        this.startTime = new Date(System.currentTimeMillis() + this.startDelay);
245                }
246                if (this.timeZone == null) {
247                        this.timeZone = TimeZone.getDefault();
248                }
249
250                CronTriggerImpl cti = new CronTriggerImpl();
251                cti.setName(this.name != null ? this.name : toString());
252                cti.setGroup(this.group);
253                if (this.jobDetail != null) {
254                        cti.setJobKey(this.jobDetail.getKey());
255                }
256                cti.setJobDataMap(this.jobDataMap);
257                cti.setStartTime(this.startTime);
258                cti.setCronExpression(this.cronExpression);
259                cti.setTimeZone(this.timeZone);
260                cti.setCalendarName(this.calendarName);
261                cti.setPriority(this.priority);
262                cti.setMisfireInstruction(this.misfireInstruction);
263                cti.setDescription(this.description);
264                this.cronTrigger = cti;
265        }
266
267
268        @Override
269        @Nullable
270        public CronTrigger getObject() {
271                return this.cronTrigger;
272        }
273
274        @Override
275        public Class<?> getObjectType() {
276                return CronTrigger.class;
277        }
278
279        @Override
280        public boolean isSingleton() {
281                return true;
282        }
283
284}