package com.brightcove.opensource.workflowengine; import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; import com.brightcove.commons.misc.logging.LogUtils; import com.brightcove.commons.xml.XalanUtils; import com.brightcove.opensource.workflowengine.record.Property; /** * <p> * This forms the base of a simple workflow engine. Actors are stringed * together as providers and consumers, each producing Record objects to * pass to the next actor in the workflow. * </p> * * <p> * The workflow will start any actor that does not have any consumers (i.e. * a terminal actor), which in turn will call all of their providers to * get records. Each provider can call their providers for data or * generate their own to pass back to all of its consumers (including * the one that called it). * </p> * * <p> * The primary use case is a single threaded workflow for now. I.e. while * the structure of the workflow allows a provider to feed multiple * consumers, the behavior of this isn't yet well tested (e.g. what happens * when a provider feeds a consumer before that consumer is "started"). * That use case is at your own risk for now. * </p> * * @author <a href="https://github.com/three4clavin">Sander Gates</a> * */ public class Workflow { private List<Actor> actors; private Logger logger; /** * <p> * Basic constructor * </p> * */ public Workflow(){ init(); } private void init(){ actors = new ArrayList<Actor>(); logger = LogUtils.getLogger(this.getClass().getCanonicalName()); } /** * <p> * Adds an actor to the workflow * </p> * * @param actor Actor to be added */ public void addActor(Actor actor){ this.actors.add(actor); } /** * <p> * Starts the workflow. All of the terminal actors will be run, which * should in turn call all of their producers. * </p> */ public void run(){ // Start all the end points, and let them start their providers for(Actor actor : actors){ if(! actor.hasConsumers()){ actor.run(); } } for(Actor actor : actors){ actor.finalize(); } } /** * <p> * Creates a new workflow from an XML configuration file. * <p> * * @param xmlFile XML configuration file * @return Workflow object * @throws ParserConfigurationException * @throws SAXException * @throws IOException * @throws TransformerException * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException * @throws SecurityException * @throws NoSuchMethodException * @throws IllegalArgumentException * @throws InvocationTargetException */ public Workflow fromXML(File xmlFile) throws ParserConfigurationException, SAXException, IOException, TransformerException, ClassNotFoundException, InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException { init(); Document doc = XalanUtils.parseXml(xmlFile, false); Element root = doc.getDocumentElement(); List<Node> actorNodes = XalanUtils.getNodesFromXPath(root, "/workflow/actor"); Map<String, Actor> actorsMap = new HashMap<String, Actor>(); for(Node actorNode : actorNodes){ String actorClassName = XalanUtils.getStringFromXPath(actorNode, "@class"); String actorName = XalanUtils.getStringFromXPath(actorNode, "@name"); Constructor<?> constructor = Class.forName(actorClassName).getConstructor(Workflow.class); Actor actor = null; try{ actor = (Actor)constructor.newInstance(this); } catch(InvocationTargetException ite){ die("Couldn't create actor '" + actorClassName + "' (" + actorClassName + "). InvocationTargetException caught: '" + ite + "' (" + ite.getCause() + ")."); } // Class<?> actorClass = Class.forName(actorClassName); // Actor actor = (Actor)actorClass.newInstance(this); actorsMap.put(actorName, actor); List<Node> propertyNodes = XalanUtils.getNodesFromXPath(actorNode, "property"); for(Node propertyNode : propertyNodes){ String propName = XalanUtils.getStringFromXPath(propertyNode, "@name"); String propVal = XalanUtils.getStringFromXPath(propertyNode, "."); Property<String> prop = new Property<String>(propName, propVal); actor.addProperty(prop); } } for(Node actorNode : actorNodes){ String actorName = XalanUtils.getStringFromXPath(actorNode, "@name"); List<Node> providers = XalanUtils.getNodesFromXPath(actorNode, "provider"); List<Node> consumers = XalanUtils.getNodesFromXPath(actorNode, "consumer"); Actor actor = actorsMap.get(actorName); for(Node providerNode : providers){ String providerName = XalanUtils.getStringFromXPath(providerNode, "@name"); Actor provider = actorsMap.get(providerName); actor.addProvider(provider); } for(Node consumerNode : consumers){ String consumerName = XalanUtils.getStringFromXPath(consumerNode, "@name"); Actor consumer = actorsMap.get(consumerName); actor.addConsumer(consumer); } } for(String actorName : actorsMap.keySet()){ addActor(actorsMap.get(actorName)); } return this; } public void log(String message){ logger.info(message); } public void logError(String message){ logger.severe(message); } public void die(String message){ logError(message); System.exit(1); } }