001package org.apache.turbine.services.schedule; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.text.ParseException; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.Set; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029import org.apache.fulcrum.quartz.QuartzScheduler; 030import org.apache.turbine.services.InitializationException; 031import org.apache.turbine.services.TurbineBaseService; 032import org.apache.turbine.services.TurbineServices; 033import org.apache.turbine.util.TurbineException; 034import org.quartz.CronScheduleBuilder; 035import org.quartz.JobBuilder; 036import org.quartz.JobDetail; 037import org.quartz.JobKey; 038import org.quartz.Scheduler; 039import org.quartz.SchedulerException; 040import org.quartz.Trigger; 041import org.quartz.TriggerBuilder; 042import org.quartz.impl.matchers.GroupMatcher; 043 044/** 045 * Service for a quartz scheduler. 046 * 047 * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a> 048 */ 049public class QuartzSchedulerService 050 extends TurbineBaseService 051 implements ScheduleService 052{ 053 /** Logging */ 054 protected static Log log = LogFactory.getLog(ScheduleService.LOGGER_NAME); 055 056 /** Current status of the scheduler */ 057 protected boolean enabled = false; 058 059 /** The Quartz scheduler instance */ 060 private Scheduler scheduler; 061 062 /** 063 * Initializes the SchedulerService. 064 * 065 * @throws InitializationException Something went wrong in the init 066 * stage 067 */ 068 @Override 069 public void init() 070 throws InitializationException 071 { 072 setEnabled(getConfiguration().getBoolean("enabled", true)); 073 QuartzScheduler qs = (QuartzScheduler) TurbineServices.getInstance() 074 .getService(QuartzScheduler.class.getName()); 075 this.scheduler = qs.getScheduler(); 076 077 restart(); 078 setInit(true); 079 } 080 081 /** 082 * Shutdowns the service. 083 * 084 * This methods interrupts the housekeeping thread. 085 */ 086 @Override 087 public void shutdown() 088 { 089 try 090 { 091 this.scheduler.shutdown(); 092 } 093 catch (SchedulerException e) 094 { 095 log.error("Could not shut down the scheduler service", e); 096 } 097 } 098 099 /** 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 * @exception 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 * @exception 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