/**
* Copyright 2011 meltmedia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xchain.framework.quartz;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
import javax.xml.namespace.QName;
import org.xchain.framework.lifecycle.LifecycleAccessor;
import org.xchain.framework.lifecycle.LifecycleContext;
import org.xchain.framework.lifecycle.LifecycleClass;
import org.xchain.framework.lifecycle.LifecycleException;
import org.xchain.framework.lifecycle.StartStep;
import org.xchain.framework.lifecycle.StopStep;
import org.xchain.framework.lifecycle.ConfigDocumentContext;
import org.xchain.annotations.Function;
import org.quartz.Scheduler;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.xml.JobSchedulingDataProcessor;
import org.quartz.simpl.ThreadContextClassLoadHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.Pointer;
/**
* <p>A lifecycle class that starts quartz, if the quartz configuration files are available on the
* class path.</p>
*
* @author Christian Trimble
* @author Josh Kennedy
* @author Devon Tackett
* @author John Trimble
*/
@LifecycleClass(uri=Constants.LIFECYCLE_URI)
public class QuartzLifecycle {
public static final Logger log = LoggerFactory.getLogger( QuartzLifecycle.class );
private static QuartzLifecycle instance = new QuartzLifecycle();
public static final String DEFAULT_QUARTZ_PROPERTIES = "quartz.properties";
public static final String DEFAULT_QUARTZ_JOB_CONFIG = "quartz-jobs.xml";
/**
* <p>Returns the singleton instance of the quartz lifecycle.</p>
*/
@LifecycleAccessor
public static QuartzLifecycle getInstance() { return instance; }
private boolean disabled = false;
private Scheduler scheduler = null;
private Integer startDelay = null;
private boolean autoStart;
/**
* <p>Returns the current default scheduler.</p>
*/
@Function(localName="scheduler")
public Scheduler getScheduler()
{
return this.scheduler;
}
/**
* <p>Prevents multiple instances of this lifecycle from being created.</p>
*/
private QuartzLifecycle() { }
/**
* <p>Configures the quartz lifecycle. This method will take several different approches to configuring the lifecycle:</p>
* <ol>
* <li>If the quartz apis cannot be found, then the QuartzLifecycle is set to disabled and this method returns.</li>
* <li>If the XChain config resource (META-INF/xchain-config.xml) contains the {http://xchain.org/quartz-config}disabled element and it has a value of "true",
* then the disabled flag is set to true and this method returns.</li>
* <li>If the XChain config resource does not contain any {http://xchain.org/quartz-config}scheduler elements, then the default scheduler is created.</li>
* <li>If the XChain config resource does contain one or more {http://xchain.org/quartz-config}scheduler elements, then schedulers are loaded for each element.</li>
* </ol>
*
* @param context the lifecycle context.
* @param configDocContext the context of the configuration document.
*/
@StartStep(localName="config", after={"{http://www.xchain.org/framework/lifecycle}config"}, xmlns={"xmlns:config='http://www.xchain.org/framework/quartz-config'"})
public void startConfig(LifecycleContext context, ConfigDocumentContext configDocContext)
throws LifecycleException
{
// test to see if the quartz lifecycle has been disabled.
Boolean disabledValue = getDisabled( configDocContext );
disabled = disabledValue!=null?disabledValue.booleanValue():false;
// if this lifecycle has been explicitly disabled, then terminate.
if( disabled ) {
if( log.isInfoEnabled() ) {
log.info("The quartz lifecycle has been disabled in the configuration file.");
}
return;
}
String propertiesResourceUrl = (String)configDocContext.getValue("/*/config:properties-url", String.class);
if (propertiesResourceUrl == null) {
log.debug("properties-url not provided. Using default location for quartz.properties");
propertiesResourceUrl = DEFAULT_QUARTZ_PROPERTIES;
}
this.startDelay = (Integer)configDocContext.getValue("/*/config:start-delay", Integer.class);
this.autoStart = (Boolean)getValue(configDocContext, "/*/config:auto-start", true, Boolean.class);
// create the scheduler factory.
StdSchedulerFactory factory = null;
InputStream defaultQuartzConfig = Thread.currentThread().getContextClassLoader().getResourceAsStream(propertiesResourceUrl);
if( defaultQuartzConfig != null ) {
log.info("Loading Quartz Config from {}", propertiesResourceUrl);
Properties properties = loadProperties(defaultQuartzConfig);
try {
factory = new StdSchedulerFactory(properties);
}
catch( Exception e ) {
throw new LifecycleException("Could not load StdSchedulerFactory for '"+propertiesResourceUrl+"'.", e);
}
}
else {
log.info("Unable to load Quartz Config. Using default scheduler factory.");
try {
factory = new StdSchedulerFactory();
}
catch( Exception e ) {
throw new LifecycleException("Could not load StdSchedulerFactory for quartz default configuration file.", e);
}
}
try {
scheduler = factory.getScheduler();
}
catch( Exception e ) {
throw new LifecycleException("Could not get the default scheduler from the scheduler factory.", e);
}
try {
// if there are any quartz-job.xml files, then load them into the scheduler.
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(DEFAULT_QUARTZ_JOB_CONFIG);
JobSchedulingDataProcessor xmlProcessor = new JobSchedulingDataProcessor(new ThreadContextClassLoadHelper(), false, false);
// Process the resources in reverse order to keep proper precedence in the classpath
Stack<URL> urlStack = new Stack<URL>();
while (urls.hasMoreElements()) {
urlStack.push(urls.nextElement());
}
for (URL resourceURL : urlStack) {
log.debug("Loading job resource {}", resourceURL.getPath());
xmlProcessor.processStream(resourceURL.openStream(), resourceURL.getPath());
xmlProcessor.scheduleJobs(xmlProcessor.getScheduledJobs(), scheduler, true);
}
} catch (Exception ex) {
throw new LifecycleException("Could not scheduler jobs from '"+DEFAULT_QUARTZ_JOB_CONFIG+"'.", ex);
} finally {
try {
defaultQuartzConfig.close();
} catch (Exception ignore) { }
}
}
@SuppressWarnings("unchecked")
public <E> E getValue(ConfigDocumentContext config, String xpath, E defaultValue, Class<E> type) {
E value = (E) config.getValue(xpath, type);
return value == null? defaultValue : value;
}
/**
* <p>Unconfigures the quartz lifecycle. The map of schedulers is cleared and the disabled flag is set back to false.</p>
*/
@StopStep(localName="config")
public void stopConfig()
{
//schedulerMap.clear();
scheduler = null;
disabled = false;
}
/**
* <p>Starts all of the schedulers registered with the quartz lifecycle.</p>
*/
@StartStep(localName="run", after={"config"})
public void startSchedulers( LifecycleContext context )
throws LifecycleException
{
if( !disabled && autoStart ) {
try {
if( this.startDelay == null )
scheduler.start();
else
scheduler.startDelayed(this.startDelay);
}
catch( Exception e ) {
throw new LifecycleException("Could not start Scheduler.", e);
}
}
}
/**
* <p>Stops all of the schedulers registered with the quartz lifecycle.</p>
*/
@StopStep(localName="run")
public void stopSchedulers( LifecycleContext context )
{
if( !disabled ) {
try {
scheduler.shutdown();
}
catch( Exception e ) {
if( log.isInfoEnabled() ) {
log.info("An exception was thrown while shutting down a quartz scheduler.", e);
}
}
}
}
/**
* <p>Loads properties from the input stream. The input stream will be closed after this operation.</p>
*/
protected static Properties loadProperties( InputStream in )
throws LifecycleException
{
Properties properties = new Properties();
try {
properties.load(in);
}
catch( Exception e ) {
try { in.close(); } catch( Exception ignore ) { }
}
return properties;
}
protected static Boolean getDisabled( ConfigDocumentContext context )
{
return (Boolean)context.getValue("/*/config:disabled", Boolean.class);
}
protected static Iterator<Pointer> getSchedulerPointerIterator( ConfigDocumentContext context )
{
return (Iterator<Pointer>)context.iteratePointers("*/config:scheduler");
}
}