001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 package org.apache.fulcrum.quartz.impl; 021 022 import java.util.ArrayList; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.Properties; 026 import java.util.Set; 027 028 import org.apache.avalon.framework.activity.Disposable; 029 import org.apache.avalon.framework.activity.Initializable; 030 import org.apache.avalon.framework.activity.Startable; 031 import org.apache.avalon.framework.configuration.Configurable; 032 import org.apache.avalon.framework.configuration.Configuration; 033 import org.apache.avalon.framework.configuration.ConfigurationException; 034 import org.apache.avalon.framework.logger.AbstractLogEnabled; 035 import org.apache.avalon.framework.logger.LogEnabled; 036 import org.apache.avalon.framework.parameters.Parameters; 037 import org.apache.avalon.framework.service.ServiceException; 038 import org.apache.avalon.framework.service.ServiceManager; 039 import org.apache.avalon.framework.service.Serviceable; 040 import org.apache.avalon.framework.thread.ThreadSafe; 041 import org.apache.fulcrum.quartz.QuartzScheduler; 042 import org.quartz.Job; 043 import org.quartz.JobDetail; 044 import org.quartz.JobExecutionContext; 045 import org.quartz.JobExecutionException; 046 import org.quartz.JobKey; 047 import org.quartz.JobListener; 048 import org.quartz.Scheduler; 049 import org.quartz.SchedulerException; 050 import org.quartz.Trigger; 051 import org.quartz.impl.StdSchedulerFactory; 052 import org.quartz.impl.matchers.GroupMatcher; 053 054 /** 055 * Avalon service wrapping the QuartzScheduler. 056 */ 057 public class QuartzSchedulerImpl 058 extends AbstractLogEnabled 059 implements QuartzScheduler, Configurable, Serviceable, Disposable, Initializable, ThreadSafe, JobListener, Startable 060 { 061 /** 062 * the Avalon service serviceManager 063 */ 064 private ServiceManager serviceManager; 065 066 /** 067 * the Quartz scheduler instance 068 */ 069 private Scheduler scheduler; 070 071 /** 072 * the quartz property file 073 */ 074 private String quartzPropertyFile; 075 076 /** 077 * the quartz properties loaded from the XML configuration 078 */ 079 private Properties quartzProperties; 080 081 // === Avalon Lifecycle ================================================= 082 083 /** 084 * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration) 085 */ 086 public void configure(Configuration conf) throws ConfigurationException 087 { 088 Configuration quartzConf = conf.getChild("configuration", true); 089 090 if(quartzConf.getChild("properties", false) != null) 091 { 092 this.quartzProperties = Parameters.toProperties(Parameters.fromConfiguration(quartzConf.getChild("properties"))); 093 } 094 else if(quartzConf.getChild("quartzPropertyFile", false) != null) 095 { 096 this.quartzPropertyFile = quartzConf.getChild("quartzPropertyFile").getValue(); 097 } 098 } 099 100 /** 101 * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager) 102 */ 103 public void service(ServiceManager manager) throws ServiceException 104 { 105 this.serviceManager = manager; 106 } 107 108 /** 109 * @see org.apache.avalon.framework.activity.Initializable#initialize() 110 */ 111 public void initialize() throws Exception 112 { 113 // instantiating a specific scheduler from a property file or properties 114 StdSchedulerFactory schedulerFactory = new StdSchedulerFactory(); 115 if(this.quartzProperties != null) 116 { 117 getLogger().info("Pulling quartz configuration from the container XML configuration"); 118 schedulerFactory.initialize(this.quartzProperties); 119 } 120 else if(this.quartzPropertyFile != null) 121 { 122 getLogger().info("Pulling quartz configuration from the following property file : " + this.quartzPropertyFile); 123 schedulerFactory.initialize(this.quartzPropertyFile); 124 } 125 else 126 { 127 getLogger().info("Using Quartz default configuration since no user-supplied configuration was found"); 128 schedulerFactory.initialize(); 129 } 130 131 this.scheduler = schedulerFactory.getScheduler(); 132 133 // add this service instance as JobListener to allow basic monitoring 134 getScheduler().getListenerManager().addJobListener(this, new ArrayList()); 135 } 136 137 public void start() throws Exception 138 { 139 getScheduler().start(); 140 141 if(getLogger().isInfoEnabled()) 142 { 143 logSchedulerConfiguration(); 144 } 145 146 } 147 148 public void stop() throws Exception 149 { 150 getScheduler().standby(); 151 } 152 153 /** 154 * @see org.apache.avalon.framework.activity.Disposable#dispose() 155 */ 156 public void dispose() 157 { 158 try 159 { 160 // shutdown() does not return until executing Jobs complete execution 161 this.scheduler.shutdown(true); 162 } 163 catch (SchedulerException e) 164 { 165 this.getLogger().warn("Problem shutting down quartz scheduler ", e); 166 } 167 168 this.scheduler = null; 169 this.serviceManager = null; 170 } 171 172 // === Service Interface Implementation ================================= 173 174 /** 175 * @see org.apache.fulcrum.quartz.QuartzScheduler#getScheduler() 176 */ 177 public Scheduler getScheduler() 178 { 179 return scheduler; 180 } 181 182 /** 183 * Calls getName() on jobListener 184 * 185 * @see org.quartz.JobListener#getName() 186 */ 187 public String getName() 188 { 189 return getClass().getName(); 190 } 191 192 /** 193 * Hook to support jobs implementing Avalon interface such as 194 * LogEnabled and Serviceable. 195 * 196 * @see org.quartz.JobListener#jobToBeExecuted(org.quartz.JobExecutionContext) 197 */ 198 public void jobToBeExecuted(JobExecutionContext context) 199 { 200 Job job = context.getJobInstance(); 201 202 // inject a logger instance 203 if(job instanceof LogEnabled) 204 { 205 ((LogEnabled) job).enableLogging(getLogger()); 206 } 207 208 // inject a ServiceManager instance 209 if (job instanceof Serviceable) 210 { 211 try 212 { 213 ((Serviceable) job).service(serviceManager); 214 } 215 catch (ServiceException e) 216 { 217 getLogger().error("Error servicing Job[" + job + "]", e); 218 } 219 } 220 } 221 222 /** 223 * @see org.quartz.JobListener#jobWasExecuted(org.quartz.JobExecutionContext, org.quartz.JobExecutionException) 224 */ 225 public void jobWasExecuted(JobExecutionContext context, JobExecutionException ex) 226 { 227 if (ex != null) 228 { 229 String msg = "Executing the job '" + context.getJobDetail().getKey() + "' failed"; 230 getLogger().error(msg, ex.getCause()); 231 } 232 else 233 { 234 if (getLogger().isDebugEnabled()) 235 { 236 getLogger().debug("Executing the job '" + context.getJobDetail().getKey() + "' took " + context.getJobRunTime() + " ms"); 237 } 238 } 239 } 240 241 /** 242 * @see org.quartz.JobListener#jobExecutionVetoed(org.quartz.JobExecutionContext) 243 */ 244 public void jobExecutionVetoed(JobExecutionContext context) 245 { 246 // nothing to do 247 } 248 249 // === Service Implementation =========================================== 250 251 private void logSchedulerConfiguration() throws SchedulerException 252 { 253 List jobGroups = getScheduler().getJobGroupNames(); 254 for (Iterator i = jobGroups.iterator(); i.hasNext();) 255 { 256 String jobGroup = (String)i.next(); 257 Set jobsInGroup = getScheduler().getJobKeys(GroupMatcher.groupEquals(jobGroup)); 258 getLogger().info("Job Group: " + jobGroup + " contains the following number of jobs : " + jobsInGroup.size()); 259 for (Iterator j = jobsInGroup.iterator(); j.hasNext();) 260 { 261 StringBuffer buffer = new StringBuffer(); 262 JobKey jobKey = (JobKey)j.next(); 263 JobDetail jobDetail = getScheduler().getJobDetail(jobKey); 264 List jobTriggers = getScheduler().getTriggersOfJob(jobKey); 265 buffer.append(jobDetail.getKey()); 266 buffer.append(" => "); 267 if(jobTriggers != null && !jobTriggers.isEmpty()) 268 { 269 Trigger jt = (Trigger)jobTriggers.get(0); 270 buffer.append(jt.getKey()); 271 buffer.append(" ("); 272 buffer.append(jt.getNextFireTime()); 273 buffer.append(")"); 274 } 275 else 276 { 277 buffer.append("no trigger defined"); 278 } 279 280 getLogger().info(buffer.toString()); 281 } 282 } 283 } 284 }