001package org.apache.fulcrum.quartz.impl;
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
022
023import java.util.ArrayList;
024import java.util.List;
025import java.util.Properties;
026import java.util.Set;
027
028import org.apache.avalon.framework.activity.Disposable;
029import org.apache.avalon.framework.activity.Initializable;
030import org.apache.avalon.framework.activity.Startable;
031import org.apache.avalon.framework.configuration.Configurable;
032import org.apache.avalon.framework.configuration.Configuration;
033import org.apache.avalon.framework.configuration.ConfigurationException;
034import org.apache.avalon.framework.logger.AbstractLogEnabled;
035import org.apache.avalon.framework.logger.LogEnabled;
036import org.apache.avalon.framework.parameters.Parameters;
037import org.apache.avalon.framework.service.ServiceException;
038import org.apache.avalon.framework.service.ServiceManager;
039import org.apache.avalon.framework.service.Serviceable;
040import org.apache.avalon.framework.thread.ThreadSafe;
041import org.apache.fulcrum.quartz.QuartzScheduler;
042import org.quartz.Job;
043import org.quartz.JobDetail;
044import org.quartz.JobExecutionContext;
045import org.quartz.JobExecutionException;
046import org.quartz.JobKey;
047import org.quartz.JobListener;
048import org.quartz.Matcher;
049import org.quartz.Scheduler;
050import org.quartz.SchedulerException;
051import org.quartz.Trigger;
052import org.quartz.impl.StdSchedulerFactory;
053import org.quartz.impl.matchers.GroupMatcher;
054
055/**
056 * Avalon service  wrapping the QuartzScheduler.
057 */
058public class QuartzSchedulerImpl
059        extends AbstractLogEnabled
060        implements QuartzScheduler, Configurable, Serviceable, Disposable, Initializable, ThreadSafe, JobListener, Startable
061{
062    /** Configuration key */
063    private static final String CONFIG_CONFIGURATION = "configuration";
064
065    /** Configuration key */
066    private static final String CONFIG_PROPERTY_FILE = "quartzPropertyFile";
067
068    /** Configuration key */
069    private static final String CONFIG_PROPERTIES = "properties";
070
071    /**
072     * the Avalon service serviceManager
073     */
074    private ServiceManager serviceManager;
075
076    /**
077     * the Quartz scheduler instance
078     */
079    private Scheduler scheduler;
080
081    /**
082     * the quartz property file
083     */
084    private String quartzPropertyFile;
085
086    /**
087     * the quartz properties loaded from the XML configuration
088     */
089    private Properties quartzProperties;
090
091    // === Avalon Lifecycle =================================================
092
093    /**
094     * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
095     */
096    @Override
097    public void configure(Configuration conf) throws ConfigurationException
098    {
099        Configuration quartzConf = conf.getChild(CONFIG_CONFIGURATION, true);
100
101        if(quartzConf.getChild(CONFIG_PROPERTIES, false) != null)
102        {
103            this.quartzProperties = Parameters.toProperties(Parameters.fromConfiguration(quartzConf.getChild(CONFIG_PROPERTIES)));
104        }
105        else if(quartzConf.getChild(CONFIG_PROPERTY_FILE, false) != null)
106        {
107            this.quartzPropertyFile = quartzConf.getChild(CONFIG_PROPERTY_FILE).getValue();
108        }
109    }
110
111    /**
112     * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
113     */
114    @Override
115    public void service(ServiceManager manager) throws ServiceException
116    {
117        this.serviceManager = manager;
118    }
119
120    /**
121     * @see org.apache.avalon.framework.activity.Initializable#initialize()
122     */
123    @Override
124    public void initialize() throws Exception
125    {
126        // instantiating a specific scheduler from a property file or properties
127        StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
128        if(this.quartzProperties != null)
129        {
130            getLogger().info("Pulling quartz configuration from the container XML configuration");
131            schedulerFactory.initialize(this.quartzProperties);
132        }
133        else if(this.quartzPropertyFile != null)
134        {
135            getLogger().info("Pulling quartz configuration from the following property file : " + this.quartzPropertyFile);
136            schedulerFactory.initialize(this.quartzPropertyFile);
137        }
138        else
139        {
140            getLogger().info("Using Quartz default configuration since no user-supplied configuration was found");
141            schedulerFactory.initialize();
142        }
143
144        this.scheduler = schedulerFactory.getScheduler();
145
146        // add this service instance as JobListener to allow basic monitoring
147        getScheduler().getListenerManager().addJobListener(this, new ArrayList<Matcher<JobKey>>());
148    }
149
150    @Override
151    public void start() throws Exception
152    {
153        getScheduler().start();
154
155        if(getLogger().isInfoEnabled())
156        {
157            logSchedulerConfiguration();
158        }
159
160    }
161
162    @Override
163    public void stop() throws Exception
164    {
165        getScheduler().standby();
166    }
167
168    /**
169     * @see org.apache.avalon.framework.activity.Disposable#dispose()
170     */
171    @Override
172    public void dispose()
173    {
174        try
175        {
176            // shutdown() does not return until executing Jobs complete execution
177            this.scheduler.shutdown(true);
178        }
179        catch (SchedulerException e)
180        {
181            this.getLogger().warn("Problem shutting down quartz scheduler ", e);
182        }
183
184        this.scheduler = null;
185        this.serviceManager = null;
186    }
187
188    // === Service Interface Implementation =================================
189
190    /**
191     * @see org.apache.fulcrum.quartz.QuartzScheduler#getScheduler()
192     */
193    @Override
194    public Scheduler getScheduler()
195    {
196        return scheduler;
197    }
198
199    /**
200     * Calls getName() on jobListener
201     *
202     * @see org.quartz.JobListener#getName()
203     */
204    @Override
205    public String getName()
206    {
207        return getClass().getName();
208    }
209
210    /**
211     * Hook to support jobs implementing Avalon interface such as
212     * LogEnabled and Serviceable.
213     *
214     * @see org.quartz.JobListener#jobToBeExecuted(org.quartz.JobExecutionContext)
215     */
216    @Override
217    public void jobToBeExecuted(JobExecutionContext context)
218    {
219        Job job = context.getJobInstance();
220
221        // inject a logger instance
222        if(job instanceof LogEnabled)
223        {
224            ((LogEnabled) job).enableLogging(getLogger());
225        }
226
227        // inject a ServiceManager instance
228        if (job instanceof Serviceable)
229        {
230            try
231            {
232                ((Serviceable) job).service(serviceManager);
233            }
234            catch (ServiceException e)
235            {
236                getLogger().error("Error servicing Job[" + job + "]", e);
237            }
238        }
239    }
240
241    /**
242     * @see org.quartz.JobListener#jobWasExecuted(org.quartz.JobExecutionContext, org.quartz.JobExecutionException)
243     */
244    @Override
245    public void jobWasExecuted(JobExecutionContext context, JobExecutionException ex)
246    {
247        if (ex != null)
248        {
249            String msg = "Executing the job '" + context.getJobDetail().getKey() + "' failed";
250            getLogger().error(msg, ex.getCause());
251        }
252        else
253        {
254            if (getLogger().isDebugEnabled())
255            {
256                getLogger().debug("Executing the job '" + context.getJobDetail().getKey() + "' took " + context.getJobRunTime() + " ms");
257            }
258        }
259    }
260
261    /**
262     * @see org.quartz.JobListener#jobExecutionVetoed(org.quartz.JobExecutionContext)
263     */
264    @Override
265    public void jobExecutionVetoed(JobExecutionContext context)
266    {
267        // nothing to do
268    }
269
270    // === Service Implementation ===========================================
271    /**
272     * @throws SchedulerException generic exception
273     */
274    private void logSchedulerConfiguration() throws SchedulerException
275    {
276        for (String jobGroup : getScheduler().getJobGroupNames())
277        {
278            Set<JobKey> jobsInGroup = getScheduler().getJobKeys(GroupMatcher.jobGroupEquals(jobGroup));
279            getLogger().info("Job Group: " + jobGroup + " contains the following number of jobs : " + jobsInGroup.size());
280            for (JobKey jobKey : jobsInGroup)
281            {
282                StringBuilder buffer = new StringBuilder();
283                JobDetail jobDetail = getScheduler().getJobDetail(jobKey);
284                List<? extends Trigger> jobTriggers = getScheduler().getTriggersOfJob(jobKey);
285                buffer.append(jobDetail.getKey());
286                buffer.append(" => ");
287                if(jobTriggers != null && !jobTriggers.isEmpty())
288                {
289                    Trigger jt = jobTriggers.get(0);
290                    buffer.append(jt.getKey());
291                    buffer.append(" (");
292                    buffer.append(jt.getNextFireTime());
293                    buffer.append(")");
294                }
295                else
296                {
297                    buffer.append("no trigger defined");
298                }
299
300                getLogger().info(buffer.toString());
301            }
302        }
303    }
304}