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.fulcrum.quartz.QuartzScheduler; 028import org.apache.logging.log4j.LogManager; 029import org.apache.logging.log4j.Logger; 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 final Logger log = LogManager.getLogger(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. Retrieves the Quartz {@link #scheduler} from the Fulcrum {@link QuartzScheduler} service. 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 * @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 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 JobEntryQuartz job = new JobEntryQuartz(trigger, jd); 411 return job; 412 } 413} 414