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   * Scheduler module. Under the node jobs one can create jobs by defining the command to use. The scheduling is done
57   * based on a cron syntax scheduling expression. The job definition can contain a params node which can hold values
58   * passed to the command in the execution-context.
59   */
60  public class SchedulerModule implements ModuleLifecycle {
61      private static final Logger log = LoggerFactory.getLogger(JobDefinition.class);
62  
63      private static SchedulerModule instance;
64  
65      private List<JobDefinition> jobs = new ArrayList<JobDefinition>();
66  
67      private boolean running = false;
68  
69      /**
70       * The quartz scheduler.
71       */
72      protected Scheduler scheduler;
73  
74      public SchedulerModule() {
75          instance = this;
76      }
77  
78      public List<JobDefinition> getJobs() {
79          return this.jobs;
80      }
81  
82      public void setJobs(List<JobDefinition> jobs) throws SchedulerException {
83          this.jobs = jobs;
84          for (JobDefinition job : jobs) {
85              if (running) {
86                  initJob(job);
87              }
88          }
89      }
90  
91      public void addJob(JobDefinition job) throws SchedulerException {
92          jobs.add(job);
93          if (running) {
94              initJob(job);
95          }
96      }
97  
98      /**
99       * Stops the scheduler.
100      */
101     @Override
102     public void stop(ModuleLifecycleContext moduleLifecycleContext) {
103         try {
104             scheduler.shutdown(true);
105             log.info("Waiting up to 30 seconds for scheduled jobs to stop...");
106             int count = 0;
107             while (count < 30) {
108                 try {
109                     Thread.sleep(1000);
110                 } catch (InterruptedException e) {
111                     Thread.interrupted();
112                 }
113                 if (scheduler.isShutdown()) {
114                     break;
115                 } else {
116                     count++;
117                 }
118             }
119             if (!scheduler.isShutdown()) {
120                 log.info("Scheduled jobs failed to finish in 30 seconds interval, forcing shutdown now...");
121                 scheduler.shutdown(false);
122             }
123             running = false;
124         } catch (SchedulerException e) {
125             log.error("Can't stop scheduler properly", e);
126         }
127     }
128 
129     /**
130      * Start scheduler and add jobs.
131      */
132     @Override
133     public void start(ModuleLifecycleContext moduleLifecycleContext) {
134         try {
135             initScheduler();
136             running = true;
137         } catch (SchedulerException e) {
138             log.error("Can't start scheduler", e);
139             return;
140         }
141 
142         initJobs();
143     }
144 
145     /**
146      * Add all the jobs defined in the jobs node.
147      */
148     protected void initJobs() {
149         for (Iterator iter = jobs.iterator(); iter.hasNext();) {
150             JobDefinition job = (JobDefinition) iter.next();
151             try {
152                 initJob(job);
153             } catch (SchedulerException e) {
154                 log.error("Can't initialize job [" + job.getName() + "]", e);
155             }
156         }
157     }
158 
159     /**
160      * Initialize a single job.
161      */
162     protected void initJob(JobDefinition job) throws SchedulerException {
163         if (job.isActive()) {
164             try {
165                 stopJob(job.getName());
166                 startJob(job);
167             } catch (SchedulerException e) {
168                 throw new SchedulerException("Can't schedule job" + job.getName(), e);
169             }
170         }
171         else {
172             try {
173                 stopJob(job.getName());
174             } catch (SchedulerException e) {
175                 throw new SchedulerException("Can't delete inactive job " + job.getName(), e);
176             }
177         }
178     }
179 
180     protected void startJob(JobDefinition job) throws SchedulerException {
181         Trigger trigger;
182         try {
183             String cron = cronToQuarzCron(job.getCron());
184             trigger = new CronTrigger(job.getName(), SchedulerConsts.SCHEDULER_GROUP_NAME, cron);
185         } catch (ParseException e) {
186             log.error("Can't parse the job's cron expression [" + job.getCron() + "]", e);
187             return;
188         }
189 
190         final Class jobClass = job.isConcurrent() ? CommandJob.class : StatefulCommandJob.class;
191         final JobDetail jd = new JobDetail(job.getName(), SchedulerConsts.SCHEDULER_GROUP_NAME, jobClass);
192         jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_COMMAND, job.getCommand());
193         jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_COMMAND_CATALOG, job.getCatalog());
194         jd.getJobDataMap().put(SchedulerConsts.CONFIG_JOB_PARAMS, job.getParams());
195         scheduler.scheduleJob(jd, trigger);
196         log.info("Job " + job.getName() + " added [" + job.getCron() + "]. Will fire first time at " + trigger.getNextFireTime());
197     }
198 
199     protected String cronToQuarzCron(String cron) {
200         // Quartz does not support * for both day of week and day of month. We simply handle this here.
201         String[] tokens = StringUtils.split(cron);
202         if (tokens.length >= 6) {
203             if (!tokens[3].equals("?") && !tokens[5].equals("?")) {
204                 if (tokens[5].equals("*")) {
205                     tokens[5] = "?";
206                 }
207                 else if (tokens[3].equals("*")) {
208                     tokens[3] = "?";
209                 }
210             }
211         }
212         cron = StringUtils.join(tokens, " ");
213         return cron;
214     }
215 
216     /**
217      * Deletes the job safely. If the job is not defined, the method does NOT throw an exception.
218      */
219     public void stopJob(String name) throws SchedulerException {
220         scheduler.deleteJob(name, SchedulerConsts.SCHEDULER_GROUP_NAME);
221     }
222 
223     /**
224      * Start the scheduler. No special configuration done yet.
225      * 
226      * @throws SchedulerException
227      */
228     protected void initScheduler() throws SchedulerException {
229         SchedulerFactory sf = new StdSchedulerFactory();
230         scheduler = sf.getScheduler();
231         scheduler.start();
232     }
233 
234     /**
235      * If you need to get the scheduler handled by the module.
236      */
237     public Scheduler getScheduler() {
238         return scheduler;
239     }
240 
241     /**
242      * Singleton instance of the module.
243      * 
244      * @deprecated since 2.0.1. Please, use IoC instead.
245      */
246     public static SchedulerModule getInstance() {
247         return instance;
248     }
249 
250 }