Java tutorial
/* * 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(); } }