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 }