/** * Copyright (C) 2004 Orbeon, Inc. * * 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.1 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. * * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html */ package org.orbeon.oxf.processor; import org.apache.log4j.Logger; import org.orbeon.dom.Document; import org.orbeon.dom.Element; import org.orbeon.dom.QName; import org.orbeon.oxf.common.OXFException; import org.orbeon.oxf.pipeline.InitUtils; import org.orbeon.oxf.externalcontext.ExternalContext; import org.orbeon.oxf.pipeline.api.PipelineContext; import org.orbeon.oxf.pipeline.api.ProcessorDefinition; import org.orbeon.oxf.util.DateUtils; import org.orbeon.oxf.util.LoggerFactory; import org.orbeon.oxf.util.task.Task; import org.orbeon.oxf.util.task.TaskScheduler; import org.orbeon.oxf.externalcontext.WebAppExternalContext; import org.orbeon.oxf.xml.XPathUtils; import org.orbeon.oxf.xml.dom4j.Dom4jUtils; import javax.servlet.http.HttpSession; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class SchedulerProcessor extends ProcessorImpl { private static final Logger logger = LoggerFactory.createLogger(SchedulerProcessor.class); public static final String SCHEDULER_CONFIG_NAMESPACE_URI = "http://www.orbeon.com/oxf/scheduler"; public SchedulerProcessor() { addInputInfo(new ProcessorInputOutputInfo(INPUT_CONFIG, SCHEDULER_CONFIG_NAMESPACE_URI)); } public void start(PipelineContext context) { try { List configs = readCacheInputAsObject(context, getInputByName(INPUT_CONFIG), new CacheableInputReader<List>() { public List read(PipelineContext context, ProcessorInput input) { List configs = new ArrayList(); Document document = readInputAsOrbeonDom(context, input); for (Iterator i = XPathUtils.selectNodeIterator(document, "/config/start-task"); i.hasNext();) { Element startTaskElement = (Element) i.next(); Config config = new Config(Config.START); config.setName(XPathUtils.selectStringValueNormalize(startTaskElement, "name")); // Create new processor definition final ProcessorDefinition processorDefinition; { // Use processor QName final Element processorNameElement = startTaskElement.element(QName.get("processor-name")); final QName processorQName = Dom4jUtils.extractTextValueQName(processorNameElement, true); processorDefinition = new ProcessorDefinition(processorQName); for (final Iterator j = XPathUtils.selectNodeIterator(startTaskElement, "input"); j.hasNext();) { Element inputElement = (Element) j.next(); String name = inputElement.attributeValue("name"); String url = inputElement.attributeValue("url"); if (url != null) { processorDefinition.addInput(name, url); } else { final Iterator it = inputElement.elementIterator(); if (it.hasNext()) { final Element srcElt = (Element) it.next(); final Element elt = (Element) srcElt.clone(); processorDefinition.addInput(name, elt); } else throw new OXFException("Node not found input element"); } } } config.setProcessorDefinition(processorDefinition); String startTimeString = XPathUtils.selectStringValueNormalize(startTaskElement, "start-time"); long startTime = 0; if ("now".equalsIgnoreCase(startTimeString)) { startTime = System.currentTimeMillis(); } else { startTime = DateUtils.parseISODateOrDateTime(startTimeString); } config.setStartTime(startTime); String interval = XPathUtils.selectStringValueNormalize(startTaskElement, "interval"); try { config.setInterval(Long.parseLong(interval)); } catch (NumberFormatException e) { throw new OXFException("Unsupported long value", e); } String sync = XPathUtils.selectStringValueNormalize(startTaskElement, "synchronized"); config.setSynchro(Boolean.valueOf(sync).booleanValue()); configs.add(config); } for (Iterator i = XPathUtils.selectNodeIterator(document, "/config/stop-task"); i.hasNext();) { Element el = (Element) i.next(); Config config = new Config(Config.STOP); config.setName(XPathUtils.selectStringValueNormalize(el, "name")); configs.add(config); } return configs; } }); ExternalContext externalContext = (ExternalContext) context.getAttribute(PipelineContext.EXTERNAL_CONTEXT); TaskScheduler scheduler = TaskScheduler.getInstance(externalContext.getWebAppContext()); //assert externalContext != null; for (Iterator i = configs.iterator(); i.hasNext();) { Config config = (Config) i.next(); switch (config.getAction()) { case Config.START: // Create processor and connect its inputs Processor processor = InitUtils.createProcessor(config.getProcessorDefinition()); processor.setId(config.getName()); // Create and schedule a task // NOTE: The ExternalContext passed: // - has visibility on the application context only // - doesn't keep references to the current context ProcessorTask task = new ProcessorTask(config.getName(), processor, config.isSynchro(), new WebAppExternalContext(externalContext.getWebAppContext(), scala.Option.apply((HttpSession) null))); task.setSchedule(config.getStartTime(), config.getInterval()); scheduler.schedule(task); break; case Config.STOP: // Find task and cancel it Task[] tasks = scheduler.getRunningTasks(); for (int ti = 0; ti < tasks.length; ti++) { if (tasks[ti] instanceof ProcessorTask && (tasks[ti]).getName().equals(config.getName())) tasks[ti].cancel(); } break; } } } catch (Exception e) { throw new OXFException(e); } } private static class ProcessorTask extends Task { private final static String RUNNING = "running"; private final static String WAITING = "waiting"; private Processor processor; private ExternalContext externalContext; private String name; private String status = WAITING; private boolean sync; public ProcessorTask(String name, Processor processor, boolean sync, ExternalContext externalContext) { this.name = name; this.processor = processor; this.sync = sync; this.externalContext = externalContext; } public String getName() { return name; } synchronized public String getStatus() { return status; } synchronized public void setStatus(boolean running) { if (running) status = RUNNING; else status = WAITING; } public void run() { try { if (sync && getStatus().equals(RUNNING)) { if (logger.isInfoEnabled()) logger.info("Task: " + getName() + " won't run since it is already running"); } else { setStatus(true); InitUtils.runProcessor(processor, externalContext, new PipelineContext(), logger); setStatus(false); } } catch (Exception e) { setStatus(false); throw new OXFException(e); } } } private static class Config { public static final int START = 0; public static final int STOP = 1; private int action; private String name; private ProcessorDefinition processorDefinition; private long startTime; private long interval; private boolean synchro = true; public Config(int action) { this.action = action; } public int getAction() { return action; } public String getName() { return name; } public void setName(String name) { this.name = name; } public long getInterval() { return interval; } public void setInterval(long interval) { this.interval = interval; } public long getStartTime() { return startTime; } public void setStartTime(long startTime) { this.startTime = startTime; } public boolean isSynchro() { return synchro; } public void setSynchro(boolean synchro) { this.synchro = synchro; } public ProcessorDefinition getProcessorDefinition() { return processorDefinition; } public void setProcessorDefinition(ProcessorDefinition processorDefinition) { this.processorDefinition = processorDefinition; } } }