View Javadoc
1   package org.apache.turbine.services.schedule;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.text.ParseException;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.Set;
26  
27  import org.apache.fulcrum.quartz.QuartzScheduler;
28  import org.apache.logging.log4j.LogManager;
29  import org.apache.logging.log4j.Logger;
30  import org.apache.turbine.services.InitializationException;
31  import org.apache.turbine.services.TurbineBaseService;
32  import org.apache.turbine.services.TurbineServices;
33  import org.apache.turbine.util.TurbineException;
34  import org.quartz.CronScheduleBuilder;
35  import org.quartz.JobBuilder;
36  import org.quartz.JobDetail;
37  import org.quartz.JobKey;
38  import org.quartz.Scheduler;
39  import org.quartz.SchedulerException;
40  import org.quartz.Trigger;
41  import org.quartz.TriggerBuilder;
42  import org.quartz.impl.matchers.GroupMatcher;
43  
44  /**
45   * Service for a quartz scheduler.
46   *
47   * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
48   */
49  public class QuartzSchedulerService
50          extends TurbineBaseService
51          implements ScheduleService
52  {
53      /** Logging */
54      protected static final Logger log = LogManager.getLogger(ScheduleService.LOGGER_NAME);
55  
56      /** Current status of the scheduler */
57      protected boolean enabled = false;
58  
59      /** The Quartz scheduler instance */
60      private Scheduler scheduler;
61  
62      /**
63       * Initializes the SchedulerService. Retrieves the Quartz {@link #scheduler} from the Fulcrum {@link QuartzScheduler} service.
64       *
65       * @throws InitializationException Something went wrong in the init
66       *         stage
67       */
68      @Override
69      public void init()
70              throws InitializationException
71      {
72          setEnabled(getConfiguration().getBoolean("enabled", true));
73          QuartzScheduler qs = (QuartzScheduler) TurbineServices.getInstance()
74              .getService(QuartzScheduler.class.getName());
75          this.scheduler = qs.getScheduler();
76  
77          restart();
78          setInit(true);
79      }
80  
81      /**
82       * Shutdowns the service.
83       *
84       * This methods interrupts the housekeeping thread.
85       */
86      @Override
87      public void shutdown()
88      {
89          try
90          {
91              this.scheduler.shutdown();
92          }
93          catch (SchedulerException e)
94          {
95              log.error("Could not shut down the scheduler service", e);
96          }
97      }
98  
99      /**
100      * @see org.apache.turbine.services.schedule.ScheduleService#newJob(int, int, int, int, int, java.lang.String)
101      */
102     @Override
103     public JobEntry newJob(int sec, int min, int hour, int wd, int day_mo, String task) throws TurbineException
104     {
105         try
106         {
107             JobDetail jd = JobBuilder.newJob(JobEntryQuartz.class)
108                     .withIdentity(task, JobEntryQuartz.DEFAULT_JOB_GROUP_NAME)
109                     .build();
110 
111             CronScheduleBuilder csb = createCronExpression(sec, min, hour, wd, day_mo);
112 
113             Trigger t = TriggerBuilder.newTrigger()
114                     .withIdentity(task, JobEntryQuartz.DEFAULT_JOB_GROUP_NAME)
115                     .withSchedule(csb)
116                     .forJob(jd)
117                     .build();
118 
119             JobEntryQuartzhedule/JobEntryQuartz.html#JobEntryQuartz">JobEntryQuartz jeq = new JobEntryQuartz(t, jd);
120 
121             return jeq;
122         }
123         catch (ParseException e)
124         {
125             throw new TurbineException("Could not create scheduled job " + task, e);
126         }
127     }
128 
129     /**
130      * Create a Cron expression from separate elements
131      *
132      * @param sec Value for entry "seconds".
133      * @param min Value for entry "minutes".
134      * @param hour Value for entry "hours".
135      * @param wd Value for entry "week days".
136      * @param day_mo Value for entry "month days".
137      * @return a CronScheduleBuilder
138      * @throws ParseException if the expression is invalid
139      */
140     private CronScheduleBuilder createCronExpression(int sec, int min, int hour, int wd, int day_mo) throws ParseException
141     {
142         StringBuilder sb = new StringBuilder();
143         sb.append(sec == -1 ? "*" : String.valueOf(sec)).append(' ');
144         sb.append(min == -1 ? "*" : String.valueOf(min)).append(' ');
145         sb.append(hour == -1 ? "*" : String.valueOf(hour)).append(' ');
146         if (day_mo == -1)
147         {
148             sb.append(wd == -1 ? "*" : "?").append(' ');
149         }
150         else
151         {
152             sb.append(day_mo).append(' ');
153         }
154         sb.append("* "); // Month not supported
155         if (day_mo == -1)
156         {
157             sb.append(wd == -1 ? "?" : String.valueOf(wd));
158         }
159         else
160         {
161             sb.append("*");
162         }
163 
164         return CronScheduleBuilder.cronSchedule(sb.toString());
165     }
166 
167     /**
168      * Get a specific Job from Storage.
169      *
170      * @param oid The int id for the job.
171      * @return A JobEntry.
172      * @throws TurbineException job could not be retrieved.
173      */
174     @Override
175     public JobEntry getJob(int oid)
176             throws TurbineException
177     {
178         for (JobEntry je : listJobs())
179         {
180             if (je.getJobId() == oid)
181             {
182                 return je;
183             }
184         }
185 
186         throw new TurbineException("Could not retrieve scheduled job with id " + oid);
187     }
188 
189     /**
190      * Add a new job to the queue.
191      *
192      * @param je A JobEntry with the job to add.
193      * @throws TurbineException job could not be added
194      */
195     @Override
196     public void addJob(JobEntry je)
197             throws TurbineException
198     {
199         try
200         {
201             // Update the scheduler.
202             JobEntryQuartz jq = downCast(je);
203             this.scheduler.scheduleJob(jq.getJobDetail(), jq.getJobTrigger());
204         }
205         catch (SchedulerException e)
206         {
207             throw new TurbineException("Problem adding Scheduled Job: " + je.getTask(), e);
208         }
209     }
210 
211     /**
212      * Remove a job from the queue.
213      *
214      * @param je A JobEntry with the job to remove.
215      * @throws TurbineException job could not be removed
216      */
217     @Override
218     public void removeJob(JobEntry je)
219             throws TurbineException
220     {
221         try
222         {
223             JobEntryQuartz jq = downCast(je);
224             this.scheduler.deleteJob(jq.getJobTrigger().getJobKey());
225 
226         }
227         catch (SchedulerException e)
228         {
229             throw new TurbineException("Problem removing Scheduled Job: " + je.getTask(), e);
230         }
231     }
232 
233     /**
234      * Add or update a job.
235      *
236      * @param je A JobEntry with the job to modify
237      * @throws TurbineException job could not be updated
238      */
239     @Override
240     public void updateJob(JobEntry je)
241             throws TurbineException
242     {
243         try
244         {
245             // Update the scheduler.
246             JobEntryQuartz jq = downCast(je);
247             this.scheduler.rescheduleJob(jq.getJobTrigger().getKey(), jq.getJobTrigger());
248         }
249         catch (SchedulerException e)
250         {
251             throw new TurbineException("Problem updating Scheduled Job: " + je.getTask(), e);
252         }
253     }
254 
255     /**
256      * List jobs in the queue.  This is used by the scheduler UI.
257      *
258      * @return A List of jobs.
259      */
260     @Override
261     public List<? extends JobEntry> listJobs()
262     {
263         List<JobEntryQuartz> jobs = new ArrayList<>();
264 
265         try
266         {
267             GroupMatcher<JobKey> groupMatcher = GroupMatcher.groupEquals(JobEntryQuartz.DEFAULT_JOB_GROUP_NAME);
268             Set<JobKey> jobKeys = scheduler.getJobKeys(groupMatcher);
269             for (JobKey jk : jobKeys)
270             {
271                 List<? extends Trigger> triggers = this.scheduler.getTriggersOfJob(jk);
272 
273                 if (triggers == null || triggers.isEmpty())
274                 {
275                     continue; // skip
276                 }
277                 JobDetail jd = this.scheduler.getJobDetail(jk);
278                 JobEntryQuartzhedule/JobEntryQuartz.html#JobEntryQuartz">JobEntryQuartz job = new JobEntryQuartz(triggers.get(0), jd);
279                 job.setJobId(jk.hashCode());
280                 jobs.add(job);
281             }
282         }
283         catch (SchedulerException e)
284         {
285             log.error("Problem listing Scheduled Jobs", e);
286         }
287 
288         return jobs;
289     }
290 
291 
292     /**
293      * Sets the enabled status of the scheduler
294      *
295      * @param enabled true if enabled
296      *
297      */
298     protected void setEnabled(boolean enabled)
299     {
300         this.enabled = enabled;
301     }
302 
303     /**
304      * Determines if the scheduler service is currently enabled.
305      *
306      * @return Status of the scheduler service.
307      */
308     @Override
309     public boolean isEnabled()
310     {
311         return enabled;
312     }
313 
314     /**
315      * Starts or restarts the scheduler if not already running.
316      */
317     @Override
318     public synchronized void startScheduler()
319     {
320         setEnabled(true);
321         restart();
322     }
323 
324     /**
325      * Stops the scheduler if it is currently running.
326      */
327     @Override
328     public synchronized void stopScheduler()
329     {
330         log.info("Stopping job scheduler");
331         try
332         {
333             this.scheduler.standby();
334             enabled = false;
335         }
336         catch (SchedulerException e)
337         {
338             log.error("Could not stop scheduler", e);
339         }
340     }
341 
342     /**
343      * Start (or restart) a thread to process commands, or wake up an
344      * existing thread if one is already running.  This method can be
345      * invoked if the background thread crashed due to an
346      * unrecoverable exception in an executed command.
347      */
348     public synchronized void restart()
349     {
350         if (enabled)
351         {
352             log.info("Starting job scheduler");
353             try
354             {
355                 if (!this.scheduler.isStarted())
356                 {
357                     this.scheduler.start();
358                 }
359                 else
360                 {
361                     notify();
362                 }
363             }
364             catch (SchedulerException e)
365             {
366                 log.error("Could not start scheduler", e);
367             }
368         }
369     }
370 
371     /**
372      * @param je a generic job entry
373      * @throws TurbineException
374      *
375      * @return A downcasted JobEntry type
376      */
377     private JobEntryQuartz downCast(JobEntry je) throws TurbineException
378     {
379         if (je instanceof JobEntryQuartz)
380         {
381             return (JobEntryQuartz)je;
382         }
383         else
384         {
385             throw new TurbineException("Invalid job type for this scheduler " + je.getClass());
386         }
387     }
388 
389     /**
390      * Exposing the Quartz scheduler to handle jobs/triggers in more detail.
391      *
392      * @return the {@link Scheduler} of this service.
393      */
394 	public Scheduler getScheduler()
395 	{
396 		return scheduler;
397 	}
398 
399 	/**
400 	 * Builds a {@link JobEntryQuartz} from Quartz trigger/job.
401 	 *
402 	 * The developer should be aware to set identity/context properly, i.e. to
403      * {@link JobEntryQuartz#DEFAULT_JOB_GROUP_NAME}, if adding triggers/jobs.
404 	 *
405 	 * @param trigger a Quartz {@link Trigger}.
406 	 * @param jd a Quartz {@link JobDetail} (built from a {@link org.quartz.Job} with {@link JobBuilder}).
407 	 * @return A JobEntryQuartz.
408 	 */
409 	public JobEntryQuartz buildJobEntry(Trigger trigger, JobDetail jd) {
410         JobEntryQuartzhedule/JobEntryQuartz.html#JobEntryQuartz">JobEntryQuartz job = new JobEntryQuartz(trigger, jd);
411 		return job;
412 	}
413 }
414