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    }