ch.entwine.weblounge.common.impl.scheduler.QuartzJob.java Source code

Java tutorial

Introduction

Here is the source code for ch.entwine.weblounge.common.impl.scheduler.QuartzJob.java

Source

/*
 *  Weblounge: Web Content Management System
 *  Copyright (c) 2003 - 2011 The Weblounge Team
 *  http://entwinemedia.com/weblounge
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software Foundation
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

package ch.entwine.weblounge.common.impl.scheduler;

import ch.entwine.weblounge.common.Customizable;
import ch.entwine.weblounge.common.impl.util.config.OptionsHelper;
import ch.entwine.weblounge.common.impl.util.xml.XPathHelper;
import ch.entwine.weblounge.common.scheduler.Job;
import ch.entwine.weblounge.common.scheduler.JobTrigger;
import ch.entwine.weblounge.common.scheduler.JobWorker;
import ch.entwine.weblounge.common.site.Environment;

import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Node;

import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;

/**
 * Base implementation for jobs.
 */
public final class QuartzJob implements Job, Customizable {

    /** The job identifier */
    protected String identifier = null;

    /** The job name */
    protected String name = null;

    /** The actual job implementation */
    protected Class<? extends JobWorker> worker = null;

    /** Job trigger */
    protected JobTrigger trigger = null;

    /** The options */
    protected OptionsHelper options = null;

    /** Job context map */
    protected Dictionary<String, Object> ctx = null;

    /** The environment */
    protected Environment environment = Environment.Production;

    /**
     * Creates a new job.
     * 
     * @param identifier
     *          job identifier
     * @param worker
     *          the job implementation
     * @param trigger
     *          the job trigger
     */
    public QuartzJob(String identifier, Class<? extends JobWorker> worker, JobTrigger trigger) {
        this(identifier, worker, null, trigger);
    }

    /**
     * Creates a new job with an initial job context. That context will be passed
     * every time the {@link #execute(Dictionary)} method is triggered.
     * 
     * @param identifier
     *          job identifier
     * @param worker
     *          the job implementation
     * @param context
     *          the job context
     * @param trigger
     *          the job trigger
     */
    public QuartzJob(String identifier, Class<? extends JobWorker> worker, Dictionary<String, Object> context,
            JobTrigger trigger) {
        if (identifier == null)
            throw new IllegalArgumentException("Job identifier must not be null");
        if (worker == null)
            throw new IllegalArgumentException("Worker must not be null");
        if (trigger == null)
            throw new IllegalArgumentException("Trigger must not be null");
        this.identifier = identifier;
        this.worker = worker;
        this.trigger = trigger;
        this.ctx = context;
        if (this.ctx == null)
            ctx = new Hashtable<String, Object>();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.scheduler.Job#setEnvironment(ch.entwine.weblounge.common.site.Environment)
     */
    public void setEnvironment(Environment environment) {
        options.setEnvironment(environment);
        ctx = new Hashtable<String, Object>();
        for (String name : options.getOptionNames()) {
            String[] values = options.getOptionValues(name);
            if (values.length == 1)
                ctx.put(name, values[0]);
            else
                ctx.put(name, values);
        }
    }

    /**
     * Sets the job identifier.
     * 
     * @param identifier
     *          the job identifier
     * @throws IllegalArgumentException
     *           if <code>identifier</code> is <code>null</code>
     */
    public void setIdentifier(String identifier) {
        if (identifier == null)
            throw new IllegalArgumentException("Job identifier must not be null");
        this.identifier = identifier;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.scheduler.Job#getIdentifier()
     */
    public String getIdentifier() {
        return identifier;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.scheduler.Job#setName(java.lang.String)
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.scheduler.Job#getName()
     */
    public String getName() {
        return name;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.scheduler.Job#setWorker(java.lang.Class)
     */
    public void setWorker(Class<JobWorker> job) {
        if (worker == null)
            throw new IllegalArgumentException("Job implementation must not be null");
        this.worker = job;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.scheduler.Job#getWorker()
     */
    public Class<? extends JobWorker> getWorker() {
        return worker;
    }

    /**
     * Returns the job context.
     * 
     * @return the context
     */
    public Dictionary<String, Object> getContext() {
        return ctx;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.scheduler.Job#setTrigger(ch.entwine.weblounge.common.scheduler.JobTrigger)
     */
    public void setTrigger(JobTrigger trigger) {
        if (trigger == null)
            throw new IllegalArgumentException("Job trigger must not be null");
        this.trigger = trigger;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.scheduler.Job#getTrigger()
     */
    public JobTrigger getTrigger() {
        return trigger;
    }

    /**
     * Resets the job, which means that there will be no more evidence that the
     * job has been run already. This is especially useful in the case where a
     * site is stopped and restarted for maintenance.
     */
    public void reset() {
        trigger.reset();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.site.Action#setOption(java.lang.String,
     *      java.lang.String)
     */
    public void setOption(String key, String value) {
        options.setOption(key, value);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#setOption(java.lang.String,
     *      java.lang.String, ch.entwine.weblounge.common.site.Environment)
     */
    public void setOption(String name, String value, Environment environment) {
        options.setOption(name, value, environment);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#getOptionValue(java.lang.String)
     */
    public String getOptionValue(String name) {
        return options.getOptionValue(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#getOptionValue(java.lang.String,
     *      java.lang.String)
     */
    public String getOptionValue(String name, String defaultValue) {
        return options.getOptionValue(name, defaultValue);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#getOptionValues(java.lang.String)
     */
    public String[] getOptionValues(String name) {
        return options.getOptionValues(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#getOptions()
     */
    public Map<String, Map<Environment, List<String>>> getOptions() {
        return options.getOptions();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#hasOption(java.lang.String)
     */
    public boolean hasOption(String name) {
        return options.hasOption(name);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#getOptionNames()
     */
    public String[] getOptionNames() {
        return options.getOptionNames();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.Customizable#removeOption(java.lang.String)
     */
    public void removeOption(String name) {
        options.removeOption(name);
    }

    /**
     * Returns the string representation of this job, which is equal to the value
     * returned by <code>getName()</code>.
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuffer buf = new StringBuffer(identifier);
        buf.append(" [schedule=");
        buf.append(trigger);
        buf.append("; class=");
        buf.append(worker.getClass().getName());
        buf.append("]");
        return buf.toString();
    }

    /**
     * Initializes this job from an XML node that was generated using
     * {@link #toXml()}.
     * <p>
     * To speed things up, you might consider using the second signature that uses
     * an existing <code>XPath</code> instance instead of creating a new one.
     * 
     * @param context
     *          the job node
     * @throws IllegalStateException
     *           if the job cannot be parsed
     * @see #fromXml(Node, XPath)
     * @see #toXml()
     */
    public static Job fromXml(Node context) throws IllegalStateException {
        XPath xpath = XPathFactory.newInstance().newXPath();
        return fromXml(context, xpath);
    }

    /**
     * Initializes this job from an XML node that was generated using
     * {@link #toXml()}.
     * 
     * @param context
     *          the job node
     * @param xpathProcessor
     *          xpath processor to use
     * @throws IllegalStateException
     *           if the job cannot be parsed
     * @see #toXml()
     */
    @SuppressWarnings("unchecked")
    public static Job fromXml(Node config, XPath xPathProcessor) throws IllegalStateException {

        CronJobTrigger jobTrigger = null;
        Dictionary<String, Object> ctx = new Hashtable<String, Object>();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        // Main attributes
        String identifier = XPathHelper.valueOf(config, "@id", xPathProcessor);

        // Implementation class
        String className = XPathHelper.valueOf(config, "m:class", xPathProcessor);
        Class<? extends JobWorker> c;
        try {
            c = (Class<? extends JobWorker>) classLoader.loadClass(className);
            c.newInstance(); // Create an instance just to make sure we catch any errors right here
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(
                    "Implementation " + className + " for job '" + identifier + "' not found", e);
        } catch (InstantiationException e) {
            throw new IllegalStateException(
                    "Error instantiating impelementation " + className + " for job '" + identifier + "'", e);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(
                    "Access violation instantiating implementation " + className + " for job '" + identifier + "'",
                    e);
        } catch (Throwable t) {
            throw new IllegalStateException(
                    "Error loading implementation " + className + " for job '" + identifier + "'", t);
        }

        // Read execution schedule
        String schedule = XPathHelper.valueOf(config, "m:schedule", xPathProcessor);
        if (schedule == null)
            throw new IllegalStateException("No schedule has been defined for job '" + identifier + "'");
        jobTrigger = new CronJobTrigger(schedule);

        // Read options
        Node nodes = XPathHelper.select(config, "m:options", xPathProcessor);
        OptionsHelper options = OptionsHelper.fromXml(nodes, xPathProcessor);
        for (Map.Entry<String, Map<Environment, List<String>>> entry : options.getOptions().entrySet()) {
            String key = entry.getKey();
            Map<Environment, List<String>> environments = entry.getValue();
            for (Environment environment : environments.keySet()) {
                List<String> values = environments.get(environment);
                if (values.size() == 1)
                    ctx.put(key, values.get(0));
                else
                    ctx.put(key, values.toArray(new String[values.size()]));
            }
        }

        // Did we find something?

        QuartzJob job = new QuartzJob(identifier, c, ctx, jobTrigger);
        job.options = options;

        // name
        String name = XPathHelper.valueOf(config, "m:name", xPathProcessor);
        job.setName(name);

        return job;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.scheduler.Job#toXml()
     */
    public String toXml() {
        StringBuffer b = new StringBuffer();
        b.append("<job id=\"");
        b.append(identifier);
        b.append("\">");

        // Names
        if (StringUtils.isNotBlank(name)) {
            b.append("<name><![CDATA[");
            b.append(name);
            b.append("]]></name>");
        }

        // Class
        b.append("<class>");
        b.append(worker.getName());
        b.append("</class>");

        // Schedule
        if (!(trigger instanceof CronJobTrigger))
            throw new IllegalStateException("Cannot serialize job trigger of type " + trigger.getClass().getName());
        b.append("<schedule>");
        b.append(((CronJobTrigger) trigger).getCronExpression());
        b.append("</schedule>");

        // Options
        Enumeration<String> e = ctx.keys();
        if (e.hasMoreElements()) {
            b.append("<options>");
            while (e.hasMoreElements()) {
                String key = e.nextElement();
                b.append("<option>");
                b.append("<name>");
                b.append(key);
                b.append("</name>");
                Object value = ctx.get(key);
                if (value instanceof String[]) {
                    String[] values = (String[]) ctx.get(key);
                    for (String v : values) {
                        b.append("<value><![CDATA[");
                        b.append(v);
                        b.append("]]></value>");
                    }
                } else {
                    b.append("<value><![CDATA[");
                    b.append(value);
                    b.append("]]></value>");
                }
                b.append("</option>");
            }
            b.append("</options>");
        }

        b.append("</job>");
        return b.toString();
    }

}