Coverage Report - org.apache.turbine.services.schedule.QuartzSchedulerService
 
Classes in this File Line Coverage Branch Coverage Complexity
QuartzSchedulerService
70%
70/100
56%
17/30
2,882
 
 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  8
 public class QuartzSchedulerService
 50  
         extends TurbineBaseService
 51  
         implements ScheduleService
 52  
 {
 53  
     /** Logging */
 54  8
     protected static Log log = LogFactory.getLog(ScheduleService.LOGGER_NAME);
 55  
 
 56  
     /** Current status of the scheduler */
 57  8
     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  11
         setEnabled(getConfiguration().getBoolean("enabled", true));
 73  11
         QuartzScheduler qs = (QuartzScheduler) TurbineServices.getInstance()
 74  
             .getService(QuartzScheduler.class.getName());
 75  11
         this.scheduler = qs.getScheduler();
 76  
 
 77  11
         restart();
 78  11
         setInit(true);
 79  11
     }
 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  11
             this.scheduler.shutdown();
 92  
         }
 93  0
         catch (SchedulerException e)
 94  
         {
 95  0
             log.error("Could not shut down the scheduler service", e);
 96  11
         }
 97  11
     }
 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  1
             JobDetail jd = JobBuilder.newJob(JobEntryQuartz.class)
 108  
                     .withIdentity(task, JobEntryQuartz.DEFAULT_JOB_GROUP_NAME)
 109  
                     .build();
 110  
 
 111  1
             CronScheduleBuilder csb = createCronExpression(sec, min, hour, wd, day_mo);
 112  
 
 113  1
             Trigger t = TriggerBuilder.newTrigger()
 114  
                     .withIdentity(task, JobEntryQuartz.DEFAULT_JOB_GROUP_NAME)
 115  
                     .withSchedule(csb)
 116  
                     .forJob(jd)
 117  
                     .build();
 118  
 
 119  1
             JobEntryQuartz jeq = new JobEntryQuartz(t, jd);
 120  
 
 121  1
             return jeq;
 122  
         }
 123  0
         catch (ParseException e)
 124  
         {
 125  0
             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  1
         StringBuilder sb = new StringBuilder();
 143  1
         sb.append(sec == -1 ? "*" : String.valueOf(sec)).append(' ');
 144  1
         sb.append(min == -1 ? "*" : String.valueOf(min)).append(' ');
 145  1
         sb.append(hour == -1 ? "*" : String.valueOf(hour)).append(' ');
 146  1
         if (day_mo == -1)
 147  
         {
 148  1
             sb.append(wd == -1 ? "*" : "?").append(' ');
 149  
         }
 150  
         else
 151  
         {
 152  0
             sb.append(day_mo).append(' ');
 153  
         }
 154  1
         sb.append("* "); // Month not supported
 155  1
         if (day_mo == -1)
 156  
         {
 157  1
             sb.append(wd == -1 ? "?" : String.valueOf(wd));
 158  
         }
 159  
         else
 160  
         {
 161  0
             sb.append("*");
 162  
         }
 163  
 
 164  1
         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  1
         for (JobEntry je : listJobs())
 179  
         {
 180  1
             if (je.getJobId() == oid)
 181  
             {
 182  1
                 return je;
 183  
             }
 184  0
         }
 185  
 
 186  0
         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  1
             JobEntryQuartz jq = downCast(je);
 203  1
             this.scheduler.scheduleJob(jq.getJobDetail(), jq.getJobTrigger());
 204  
         }
 205  0
         catch (SchedulerException e)
 206  
         {
 207  0
             throw new TurbineException("Problem adding Scheduled Job: " + je.getTask(), e);
 208  1
         }
 209  1
     }
 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  1
             JobEntryQuartz jq = downCast(je);
 224  1
             this.scheduler.deleteJob(jq.getJobTrigger().getJobKey());
 225  
 
 226  
         }
 227  0
         catch (SchedulerException e)
 228  
         {
 229  0
             throw new TurbineException("Problem removing Scheduled Job: " + je.getTask(), e);
 230  1
         }
 231  1
     }
 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  0
             JobEntryQuartz jq = downCast(je);
 247  0
             this.scheduler.rescheduleJob(jq.getJobTrigger().getKey(), jq.getJobTrigger());
 248  
         }
 249  0
         catch (SchedulerException e)
 250  
         {
 251  0
             throw new TurbineException("Problem updating Scheduled Job: " + je.getTask(), e);
 252  0
         }
 253  0
     }
 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  4
         List<JobEntryQuartz> jobs = new ArrayList<JobEntryQuartz>();
 264  
 
 265  
         try
 266  
         {
 267  
             @SuppressWarnings("unchecked") // See QTZ-184
 268  4
             GroupMatcher<JobKey> groupMatcher = GroupMatcher.groupEquals(JobEntryQuartz.DEFAULT_JOB_GROUP_NAME);
 269  4
             Set<JobKey> jobKeys = scheduler.getJobKeys(groupMatcher);
 270  4
             for (JobKey jk : jobKeys)
 271  
             {
 272  5
                 List<? extends Trigger> triggers = this.scheduler.getTriggersOfJob(jk);
 273  
 
 274  5
                 if (triggers == null || triggers.isEmpty())
 275  
                 {
 276  0
                     continue; // skip
 277  
                 }
 278  5
                 JobDetail jd = this.scheduler.getJobDetail(jk);
 279  5
                 JobEntryQuartz job = new JobEntryQuartz(triggers.get(0), jd);
 280  5
                 job.setJobId(jk.hashCode());
 281  5
                 jobs.add(job);
 282  5
             }
 283  
         }
 284  0
         catch (SchedulerException e)
 285  
         {
 286  0
             log.error("Problem listing Scheduled Jobs", e);
 287  4
         }
 288  
 
 289  4
         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  12
         this.enabled = enabled;
 302  12
     }
 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  2
         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  1
         setEnabled(true);
 322  1
         restart();
 323  1
     }
 324  
 
 325  
     /**
 326  
      * Stops the scheduler if it is currently running.
 327  
      */
 328  
     @Override
 329  
     public synchronized void stopScheduler()
 330  
     {
 331  1
         log.info("Stopping job scheduler");
 332  
         try
 333  
         {
 334  1
             this.scheduler.standby();
 335  1
             enabled = false;
 336  
         }
 337  0
         catch (SchedulerException e)
 338  
         {
 339  0
             log.error("Could not stop scheduler", e);
 340  1
         }
 341  1
     }
 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  12
         if (enabled)
 352  
         {
 353  1
             log.info("Starting job scheduler");
 354  
             try
 355  
             {
 356  1
                 if (!this.scheduler.isStarted())
 357  
                 {
 358  0
                     this.scheduler.start();
 359  
                 }
 360  
                 else
 361  
                 {
 362  1
                     notify();
 363  
                 }
 364  
             }
 365  0
             catch (SchedulerException e)
 366  
             {
 367  0
                 log.error("Could not start scheduler", e);
 368  1
             }
 369  
         }
 370  12
     }
 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  2
         if (je instanceof JobEntryQuartz)
 381  
         {
 382  2
             return (JobEntryQuartz)je;
 383  
         }
 384  
         else
 385  
         {
 386  0
             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  0
                 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  0
         JobEntryQuartz job = new JobEntryQuartz(trigger, jd);
 412  0
                 return job;
 413  
         }
 414  
 }
 415