View Javadoc

1   /**
2    * This file Copyright (c) 2006-2012 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.scheduler;
35  
36  import info.magnolia.module.ModuleLifecycle;
37  import info.magnolia.module.ModuleLifecycleContext;
38  
39  import java.text.ParseException;
40  import java.util.ArrayList;
41  import java.util.Iterator;
42  import java.util.List;
43  
44  import org.apache.commons.lang.StringUtils;
45  import org.quartz.CronTrigger;
46  import org.quartz.JobDetail;
47  import org.quartz.Scheduler;
48  import org.quartz.SchedulerException;
49  import org.quartz.SchedulerFactory;
50  import org.quartz.Trigger;
51  import org.quartz.impl.StdSchedulerFactory;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  
56  /**
57   * Scheduler module. Under the node jobs one can create jobs by defining the command to use. The scheduling is done
58   * based on a cron syntax scheduling expression. The job definition can contain a params node which can hold values
59   * passed to the command in the execution-context.
60   *
61   * @author philipp
62   * @version $Id$
63   */
64  public class SchedulerModule implements ModuleLifecycle {
65      private static final Logger log = LoggerFactory.getLogger(JobDefinition.class);
66  
67      private static SchedulerModule instance;
68  
69      private List<JobDefinition> jobs = new ArrayList<JobDefinition>();
70  
71      private boolean running = false;
72  
73      /**
74       * The quartz scheduler.
75       */
76      protected Scheduler scheduler;
77  
78      public SchedulerModule() {
79          instance = this;
80      }
81  
82  
83      public List<JobDefinition> getJobs() {
84          return this.jobs;
85      }
86  
87  
88      public void setJobs(List<JobDefinition> jobs) throws SchedulerException {
89          this.jobs = jobs;
90          for (JobDefinition job : jobs) {
91              if (running) {
92                  initJob(job);
93              }
94          }
95      }
96  
97      public void addJob(JobDefinition job) throws SchedulerException{
98          jobs.add(job);
99          if(running){
100             initJob(job);
101         }
102     }
103 
104     /**
105      * Stops the scheduler.
106      */
107     @Override
108     public void stop(ModuleLifecycleContext moduleLifecycleContext) {
109         try {
110             scheduler.shutdown(true);
111             log.info("Waiting up to 30 seconds for scheduled jobs to stop...");
112             int count = 0;
113             while (count < 30) {
114                 try {
115                     Thread.sleep(1000);
116                 } catch (InterruptedException e) {
117                     Thread.interrupted();
118                 }
119                 if (scheduler.isShutdown()) {
120                     break;
121                 } else {
122                     count++;
123                 }
124             }
125             if (!scheduler.isShutdown()) {
126                 log.info("Scheduled jobs failed to finish in 30 seconds interval, forcing shutdown now...");
127                 scheduler.shutdown(false);
128             }
129             running = false;
130         }
131         catch (SchedulerException e) {
132             log.error("Can't stop scheduler properly", e);
133         }
134     }
135 
136     /**
137      * Start scheduler and add jobs.
138      */
139     @Override
140     public void start(ModuleLifecycleContext moduleLifecycleContext) {
141         try {
142             initScheduler();
143             running = true;
144         }
145         catch (SchedulerException e) {
146             log.error("Can't start scheduler", e);
147             return;
148         }
149 
150         initJobs();
151     }
152 
153     /**
154      * Add all the jobs defined in the jobs node.
155      */
156     protected void initJobs() {
157         for (Iterator iter = jobs.iterator(); iter.hasNext();) {
158             JobDefinition job = (JobDefinition) iter.next();
159             try {
160                 initJob(job);
161             }
162             catch (SchedulerException e) {
163                 log.error("Can't initialize job [" + job.getName() + "]", e);
164             }
165         }
166     }
167 
168     /**
169      * Initialize a single job.
170      */
171     protected void initJob(JobDefinition job) throws SchedulerException {
172         if (job.isActive()) {
173             try {
174                 stopJob(job.getName());
175                 startJob(job);
176             }
177             catch (SchedulerException e) {
178                 throw new SchedulerException("Can't schedule job" + job.getName(), e);
179             }
180         }
181         else {
182             try {
183                 stopJob(job.getName());
184             } catch (SchedulerException e) {
185                 throw new SchedulerException("Can't delete inactive job " + job.getName(), e);
186             }
187         }
188     }
189 
190     protected void startJob(JobDefinition job) throws SchedulerException {
191         Trigger trigger;
192         try {
193             String cron = cronToQuarzCron(job.getCron());
194             trigger = new CronTrigger(job.getName(), SchedulerConsts.SCHEDULER_GROUP_NAME, cron);
195         }
196         catch (ParseException e) {
197             log.error("Can't parse the job's cron expression [" + job.getCron() + "]", e);
198             return;
199         }
200 
201         final Class jobClass = job.isConcurrent() ? CommandJob.class : StatefulCommandJob.class;
202         final JobDetail jd = new JobDetail(job.getName(), SchedulerConsts.SCHEDULER_GROUP_NAME, jobClass);
203         jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_COMMAND, job.getCommand());
204         jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_COMMAND_CATALOG, job.getCatalog());
205         jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_PARAMS, job.getParams());
206         scheduler.scheduleJob(jd, trigger);
207         log.info("Job " + job.getName() + " added [" + job.getCron() + "]. Will fire first time at " + trigger.getNextFireTime());
208     }
209 
210 
211     protected String cronToQuarzCron(String cron) {
212         // Quartz does not support * for both day of week and day of month. We simply handle this here.
213         String[] tokens = StringUtils.split(cron);
214         if (tokens.length >= 6) {
215             if (!tokens[3].equals("?") && !tokens[5].equals("?")) {
216                 if(tokens[5].equals("*")){
217                     tokens[5] = "?";
218                 }
219                 else if(tokens[3].equals("*")){
220                     tokens[3] = "?";
221                 }
222             }
223         }
224         cron = StringUtils.join(tokens, " ");
225         return cron;
226     }
227 
228     /**
229      * Deletes the job safely. If the job is not defined, the method does NOT throw an exception.
230      */
231     public void stopJob(String name) throws SchedulerException {
232         scheduler.deleteJob(name, SchedulerConsts.SCHEDULER_GROUP_NAME);
233     }
234 
235     /**
236      * Start the scheduler. No special configuration done yet.
237      * @throws SchedulerException
238      */
239     protected void initScheduler() throws SchedulerException {
240         SchedulerFactory sf = new StdSchedulerFactory();
241         scheduler = sf.getScheduler();
242         scheduler.start();
243     }
244 
245     /**
246      * If you need to get the scheduler handled by the module.
247      */
248     public Scheduler getScheduler() {
249         return scheduler;
250     }
251 
252     /**
253      * Singleton instance of the module.
254      */
255     public static SchedulerModule getInstance() {
256         return instance;
257     }
258 
259 }