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.commons.logging.Log;
    28  import org.apache.commons.logging.LogFactory;
    29  import org.apache.fulcrum.quartz.QuartzScheduler;
    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 Log log = LogFactory.getLog(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             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<JobEntryQuartz>();
    264
    265         try
    266         {
    267             @SuppressWarnings("unchecked") // See QTZ-184
    268             GroupMatcher<JobKey> groupMatcher = GroupMatcher.groupEquals(JobEntryQuartz.DEFAULT_JOB_GROUP_NAME);
    269             Set<JobKey> jobKeys = scheduler.getJobKeys(groupMatcher);
    270             for (JobKey jk : jobKeys)
    271             {
    272                 List<? extends Trigger> triggers = this.scheduler.getTriggersOfJob(jk);
    273
    274                 if (triggers == null || triggers.isEmpty())
    275                 {
    276                     continue; // skip
    277                 }
    278                 JobDetail jd = this.scheduler.getJobDetail(jk);
    279                 JobEntryQuartz job = new JobEntryQuartz(triggers.get(0), jd);
    280                 job.setJobId(jk.hashCode());
    281                 jobs.add(job);
    282             }
    283         }
    284         catch (SchedulerException e)
    285         {
    286             log.error("Problem listing Scheduled Jobs", e);
    287         }
    288
    289         return jobs;
    290     }
    291
    292
    293     /**
    294      * Sets the enabled status of the scheduler
    295      *
    296      * @param enabled
    297      *
    298      */
    299     protected void setEnabled(boolean enabled)
    300     {
    301         this.enabled = enabled;
    302     }
    303
    304     /**
    305      * Determines if the scheduler service is currently enabled.
    306      *
    307      * @return Status of the scheduler service.
    308      */
    309     @Override
    310     public boolean isEnabled()
    311     {
    312         return enabled;
    313     }
    314
    315     /**
    316      * Starts or restarts the scheduler if not already running.
    317      */
    318     @Override
    319     public synchronized void startScheduler()
    320     {
    321         setEnabled(true);
    322         restart();
    323     }
    324
    325     /**
    326      * Stops the scheduler if it is currently running.
    327      */
    328     @Override
    329     public synchronized void stopScheduler()
    330     {
    331         log.info("Stopping job scheduler");
    332         try
    333         {
    334             this.scheduler.standby();
    335             enabled = false;
    336         }
    337         catch (SchedulerException e)
    338         {
    339             log.error("Could not stop scheduler", e);
    340         }
    341     }
    342
    343     /**
    344      * Start (or restart) a thread to process commands, or wake up an
    345      * existing thread if one is already running.  This method can be
    346      * invoked if the background thread crashed due to an
    347      * unrecoverable exception in an executed command.
    348      */
    349     public synchronized void restart()
    350     {
    351         if (enabled)
    352         {
    353             log.info("Starting job scheduler");
    354             try
    355             {
    356                 if (!this.scheduler.isStarted())
    357                 {
    358                     this.scheduler.start();
    359                 }
    360                 else
    361                 {
    362                     notify();
    363                 }
    364             }
    365             catch (SchedulerException e)
    366             {
    367                 log.error("Could not start scheduler", e);
    368             }
    369         }
    370     }
    371
    372     /**
    373      * @param je a generic job entry
    374      * @throws TurbineException
    375      *
    376      * @return A downcasted JobEntry type
    377      */
    378     private JobEntryQuartz downCast(JobEntry je) throws TurbineException
    379     {
    380         if (je instanceof JobEntryQuartz)
    381         {
    382             return (JobEntryQuartz)je;
    383         }
    384         else
    385         {
    386             throw new TurbineException("Invalid job type for this scheduler " + je.getClass());
    387         }
    388     }
    389
    390     /**
    391      * Exposing the Quartz scheduler to handle jobs/triggers in more detail.
    392      *
    393      * @return the {@link Scheduler} of this service.
    394      */
    395 	public Scheduler getScheduler()
    396 	{
    397 		return scheduler;
    398 	}
    399
    400 	/**
    401 	 * Builds a {@link JobEntryQuartz} from Quartz trigger/job.
    402 	 *
    403 	 * The developer should be aware to set identity/context properly, i.e. to
    404      * {@value JobEntryQuartz#DEFAULT_JOB_GROUP_NAME}, if adding triggers/jobs.
    405 	 *
    406 	 * @param trigger a Quartz {@link Trigger}.
    407 	 * @param jd a Quartz {@link JobDetail} (built from a {@link Job} with {@link JobBuilder}).
    408 	 * @return A JobEntryQuartz.
    409 	 */
    410 	public JobEntryQuartz buildJobEntry(Trigger trigger, JobDetail jd) {
    411         JobEntryQuartz job = new JobEntryQuartz(trigger, jd);
    412 		return job;
    413 	}
    414 }
    415