Coverage Report - org.apache.turbine.services.schedule.QuartzSchedulerService
 
Classes in this File Line Coverage Branch Coverage Complexity
QuartzSchedulerService
71%
77/107
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.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  24
 public class QuartzSchedulerService
 50  
         extends TurbineBaseService
 51  
         implements ScheduleService
 52  
 {
 53  
     /** Logging */
 54  24
     protected static final Logger log = LogManager.getLogger(ScheduleService.LOGGER_NAME);
 55  
 
 56  
     /** Current status of the scheduler */
 57  24
     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  33
         setEnabled(getConfiguration().getBoolean("enabled", true));
 73  33
         QuartzScheduler qs = (QuartzScheduler) TurbineServices.getInstance()
 74  33
             .getService(QuartzScheduler.class.getName());
 75  33
         this.scheduler = qs.getScheduler();
 76  
 
 77  33
         restart();
 78  33
         setInit(true);
 79  33
     }
 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  33
             this.scheduler.shutdown();
 92  
         }
 93  0
         catch (SchedulerException e)
 94  
         {
 95  0
             log.error("Could not shut down the scheduler service", e);
 96  33
         }
 97  33
     }
 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  3
             JobDetail jd = JobBuilder.newJob(JobEntryQuartz.class)
 108  3
                     .withIdentity(task, JobEntryQuartz.DEFAULT_JOB_GROUP_NAME)
 109  3
                     .build();
 110  
 
 111  3
             CronScheduleBuilder csb = createCronExpression(sec, min, hour, wd, day_mo);
 112  
 
 113  3
             Trigger t = TriggerBuilder.newTrigger()
 114  3
                     .withIdentity(task, JobEntryQuartz.DEFAULT_JOB_GROUP_NAME)
 115  3
                     .withSchedule(csb)
 116  3
                     .forJob(jd)
 117  3
                     .build();
 118  
 
 119  3
             JobEntryQuartz jeq = new JobEntryQuartz(t, jd);
 120  
 
 121  3
             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  3
         StringBuilder sb = new StringBuilder();
 143  3
         sb.append(sec == -1 ? "*" : String.valueOf(sec)).append(' ');
 144  3
         sb.append(min == -1 ? "*" : String.valueOf(min)).append(' ');
 145  3
         sb.append(hour == -1 ? "*" : String.valueOf(hour)).append(' ');
 146  3
         if (day_mo == -1)
 147  
         {
 148  3
             sb.append(wd == -1 ? "*" : "?").append(' ');
 149  
         }
 150  
         else
 151  
         {
 152  0
             sb.append(day_mo).append(' ');
 153  
         }
 154  3
         sb.append("* "); // Month not supported
 155  3
         if (day_mo == -1)
 156  
         {
 157  3
             sb.append(wd == -1 ? "?" : String.valueOf(wd));
 158  
         }
 159  
         else
 160  
         {
 161  0
             sb.append("*");
 162  
         }
 163  
 
 164  3
         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  3
         for (JobEntry je : listJobs())
 179  
         {
 180  3
             if (je.getJobId() == oid)
 181  
             {
 182  3
                 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  3
             JobEntryQuartz jq = downCast(je);
 203  3
             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  3
         }
 209  3
     }
 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  3
             JobEntryQuartz jq = downCast(je);
 224  3
             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  3
         }
 231  3
     }
 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  12
         List<JobEntryQuartz> jobs = new ArrayList<JobEntryQuartz>();
 264  
 
 265  
         try
 266  
         {
 267  12
             GroupMatcher<JobKey> groupMatcher = GroupMatcher.groupEquals(JobEntryQuartz.DEFAULT_JOB_GROUP_NAME);
 268  12
             Set<JobKey> jobKeys = scheduler.getJobKeys(groupMatcher);
 269  12
             for (JobKey jk : jobKeys)
 270  
             {
 271  15
                 List<? extends Trigger> triggers = this.scheduler.getTriggersOfJob(jk);
 272  
 
 273  15
                 if (triggers == null || triggers.isEmpty())
 274  
                 {
 275  0
                     continue; // skip
 276  
                 }
 277  15
                 JobDetail jd = this.scheduler.getJobDetail(jk);
 278  15
                 JobEntryQuartz job = new JobEntryQuartz(triggers.get(0), jd);
 279  15
                 job.setJobId(jk.hashCode());
 280  15
                 jobs.add(job);
 281  15
             }
 282  
         }
 283  0
         catch (SchedulerException e)
 284  
         {
 285  0
             log.error("Problem listing Scheduled Jobs", e);
 286  12
         }
 287  
 
 288  12
         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  36
         this.enabled = enabled;
 301  36
     }
 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  6
         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  3
         setEnabled(true);
 321  3
         restart();
 322  3
     }
 323  
 
 324  
     /**
 325  
      * Stops the scheduler if it is currently running.
 326  
      */
 327  
     @Override
 328  
     public synchronized void stopScheduler()
 329  
     {
 330  3
         log.info("Stopping job scheduler");
 331  
         try
 332  
         {
 333  3
             this.scheduler.standby();
 334  3
             enabled = false;
 335  
         }
 336  0
         catch (SchedulerException e)
 337  
         {
 338  0
             log.error("Could not stop scheduler", e);
 339  3
         }
 340  3
     }
 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  36
         if (enabled)
 351  
         {
 352  3
             log.info("Starting job scheduler");
 353  
             try
 354  
             {
 355  3
                 if (!this.scheduler.isStarted())
 356  
                 {
 357  0
                     this.scheduler.start();
 358  
                 }
 359  
                 else
 360  
                 {
 361  3
                     notify();
 362  
                 }
 363  
             }
 364  0
             catch (SchedulerException e)
 365  
             {
 366  0
                 log.error("Could not start scheduler", e);
 367  3
             }
 368  
         }
 369  36
     }
 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  6
         if (je instanceof JobEntryQuartz)
 380  
         {
 381  6
             return (JobEntryQuartz)je;
 382  
         }
 383  
         else
 384  
         {
 385  0
             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  0
                 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  0
         JobEntryQuartz job = new JobEntryQuartz(trigger, jd);
 411  0
                 return job;
 412  
         }
 413  
 }
 414