Anonymous avatar Anonymous committed f87076f

Initial import from SourceForge

Comments (0)

Files changed (14)

src/etc/example/example.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.5//EN" "http://www.opensymphony.com/osworkflow/workflow_2_5.dtd">
+<workflow>
+	<initial-actions>
+		<action id="1" name="Start Workflow">
+			<restrict-to>
+				<conditions type="AND">
+					<condition type="beanshell">
+						<arg name="script">true</arg>
+					</condition>
+					<condition type="class">
+						<arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
+						<arg name="group">foos</arg>
+					</condition>
+				</conditions>
+			</restrict-to>
+			<pre-functions>
+				<function type="class">
+					<arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+				</function>
+			</pre-functions>
+			<results>
+				<unconditional-result old-status="Finished" status="Underway" step="1" owner="${caller}"/>
+			</results>
+		</action>
+	</initial-actions>
+	<steps>
+		<step id="1" name="First Draft">
+			<external-permissions>
+				<permission name="permA">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+				</permission>
+			</external-permissions>
+			<actions>
+				<action id="1" name="Finish First Draft">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="beanshell">
+								<arg name="script">true</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<pre-functions>
+						<function type="beanshell">
+							<arg name="script">
+                                String caller = context.getCaller();
+                                propertySet.setString("caller", caller);
+                                boolean test = true;
+                                String yuck = null;
+                                String blah = "987654321";
+                                System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
+                            </arg>
+						</function>
+					</pre-functions>
+					<results>
+						<result old-status="Finished" split="1">
+							<conditions type="AND">
+								<condition type="beanshell">
+									<arg name="script">
+									propertySet.getString("caller").equals("test")
+									</arg>
+								</condition>
+							</conditions>
+							<post-functions>
+								<function type="beanshell">
+									<arg name="script">
+                                        System.out.println("11111111111111");
+                                        System.out.println("11111111111111");
+                                        System.out.println("11111111111111");
+                                        System.out.println("11111111111111");
+                                        System.out.println("11111111111111");
+                                        System.out.println("11111111111111");
+                                        System.out.println("11111111111111");
+                                    </arg>
+								</function>
+							</post-functions>
+						</result>
+						<unconditional-result old-status="Finished" split="2"/>
+					</results>
+					<post-functions>
+						<function type="beanshell">
+							<arg name="script">
+                                System.out.println("22222222222222");
+                                System.out.println("22222222222222");
+                                System.out.println("22222222222222");
+                                System.out.println("22222222222222");
+                                System.out.println("22222222222222");
+                                System.out.println("22222222222222");
+                                System.out.println("22222222222222");
+                                System.out.println("22222222222222");
+                                System.out.println("22222222222222");
+                            </arg>
+						</function>
+                        <!--
+                        <function type="class">
+                            <arg name="class.name">com.opensymphony.workflow.util.ScheduleJob</arg>
+                            <arg name="triggerId">1</arg>
+                            <arg name="jobName">testJob</arg>
+                            <arg name="triggerName">testTrigger</arg>
+                            <arg name="groupName">test</arg>
+
+                            <arg name="repeat">10</arg>
+                            <arg name="repeatDelay">2000</arg>
+                            <arg name="cronExpression">0,5,10,15,20,25,30,35,40,45,50,55 * * * * ?</arg>
+
+                            <arg name="username">test</arg>
+                            <arg name="password">test</arg>
+
+                            <arg name="local">true</arg>
+                            <arg name="schedulerStart">true</arg>
+                        </function>
+                        -->
+					</post-functions>
+				</action>
+			</actions>
+		</step>
+		<step id="2" name="Edit Doc">
+			<external-permissions>
+				<permission name="permB">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+
+				</permission>
+			</external-permissions>
+			<actions>
+				<action id="2" name="Sign Up For Editing">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Queued</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
+								<arg name="group">bars</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<pre-functions>
+						<function type="class">
+							<arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+						</function>
+					</pre-functions>
+					<results>
+						<unconditional-result old-status="Finished" status="Underway" step="2" owner="${caller}"/>
+					</results>
+				</action>
+				<action id="3" name="Finish Editing">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<pre-functions>
+						<function type="class">
+							<arg name="class.name">com.opensymphony.workflow.util.MostRecentOwner</arg>
+							<arg name="stepId">1</arg>
+						</function>
+					</pre-functions>
+					<results>
+						<unconditional-result old-status="Finished" status="Underway" step="3" owner="${mostRecentOwner}"/>
+					</results>
+				</action>
+				<action id="4" name="Requeue Editing">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<results>
+						<unconditional-result old-status="Finished" status="Queued" step="2"/>
+					</results>
+				</action>
+			</actions>
+		</step>
+		<step id="3" name="Review Doc">
+			<external-permissions>
+				<permission name="permA">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+				</permission>
+			</external-permissions>
+			<actions>
+				<action id="5" name="More Edits">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<pre-functions>
+						<function type="class">
+							<arg name="class.name">com.opensymphony.workflow.util.MostRecentOwner</arg>
+							<arg name="stepId">2</arg>
+						</function>
+					</pre-functions>
+					<results>
+						<unconditional-result old-status="Finished" status="Underway" step="2" owner="${mostRecentOwner}"/>
+					</results>
+				</action>
+				<action id="6" name="Peer Review">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<results>
+						<unconditional-result old-status="Finished" status="Queued" step="4"/>
+					</results>
+				</action>
+				<action id="7" name="Publish Doc">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<pre-functions>
+						<function type="class">
+							<arg name="class.name">com.opensymphony.workflow.util.MostRecentOwner</arg>
+							<arg name="stepId">2</arg>
+						</function>
+					</pre-functions>
+					<results>
+						<unconditional-result old-status="Finished" status="Underway" step="5" owner="${mostRecentOwner}"/>
+					</results>
+				</action>
+			</actions>
+		</step>
+		<step id="4" name="Second Review">
+			<external-permissions>
+				<permission name="permC">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+				</permission>
+			</external-permissions>
+			<actions>
+				<action id="8" name="Finish Second Review">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<pre-functions>
+						<function type="class">
+							<arg name="class.name">com.opensymphony.workflow.util.MostRecentOwner</arg>
+							<arg name="stepId">3</arg>
+						</function>
+					</pre-functions>
+					<results>
+						<unconditional-result old-status="Finished" status="Underway" step="3" owner="${mostRecentOwner}"/>
+					</results>
+				</action>
+				<action id="9" name="Sign Up For Second Review">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Queued</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
+								<arg name="group">bazs</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<pre-functions>
+						<function type="class">
+							<arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+						</function>
+					</pre-functions>
+					<results>
+						<unconditional-result old-status="Finished" status="Underway" step="4" owner="${caller}"/>
+					</results>
+				</action>
+				<action id="10" name="Queue Second Review">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<results>
+						<unconditional-result old-status="Finished" status="Queued" step="4"/>
+					</results>
+				</action>
+			</actions>
+		</step>
+		<step id="5" name="Publish Doc">
+			<actions>
+				<action id="11" name="Publish Document">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<pre-functions>
+						<function type="class">
+							<arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+						</function>
+					</pre-functions>
+					<results>
+						<unconditional-result old-status="Finished" status="Finished" step="5" owner="${caller}"/>
+					</results>
+				</action>
+			</actions>
+		</step>
+		<step id="6" name="Foo">
+			<actions>
+				<action name="Finish Foo" id="12">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+								<arg name="stepId">6</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+								<arg name="stepId">6</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<results>
+						<unconditional-result old-status="Finished" join="1"/>
+					</results>
+				</action>
+			</actions>
+		</step>
+		<step id="7" name="Bar">
+			<actions>
+				<action name="Finish Bar" id="13">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+								<arg name="stepId">7</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+								<arg name="stepId">7</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<results>
+						<unconditional-result old-status="Finished" owner="test" status="Underway" step="8"/>
+					</results>
+				</action>
+			</actions>
+		</step>
+		<step id="8" name="Baz">
+			<actions>
+				<action name="Finish Baz" id="14">
+					<restrict-to>
+						<conditions type="AND">
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+								<arg name="status">Underway</arg>
+								<arg name="stepId">8</arg>
+							</condition>
+							<condition type="class">
+								<arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+								<arg name="stepId">8</arg>
+							</condition>
+						</conditions>
+					</restrict-to>
+					<results>
+						<unconditional-result old-status="Finished" join="1"/>
+					</results>
+				</action>
+			</actions>
+		</step>
+	</steps>
+	<splits>
+		<split id="1">
+			<unconditional-result old-status="Finished" status="Underway" owner="test" step="6"/>
+			<unconditional-result old-status="Finished" status="Underway" owner="test" step="7"/>
+		</split>
+		<split id="2">
+			<unconditional-result old-status="Finished" status="Queued" step="2"/>
+		</split>
+	</splits>
+	<joins>
+		<join id="1">
+			<conditions type="AND">
+				<condition type="beanshell">
+					<arg name="script"><![CDATA[
+					"Finished".equals(jn.getStep(6).getStatus()) && "Finished".equals(jn.getStep(8).getStatus())
+					]]></arg>
+				</condition>
+			</conditions>
+			<unconditional-result old-status="Finished" status="Underway" owner="test" step="2"/>
+		</join>
+	</joins>
+</workflow>
+

src/java/com/opensymphony/workflow/loader/URLWorkflowFactory.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow.loader;
+
+import com.opensymphony.workflow.FactoryException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.*;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * @author Hani Suleiman
+ * Date: May 10, 2002
+ * Time: 11:59:47 AM
+ */
+public class URLWorkflowFactory extends AbstractWorkflowFactory {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+    private static final Log log = LogFactory.getLog(URLWorkflowFactory.class);
+
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    private Map cache = new HashMap();
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public WorkflowDescriptor getWorkflow(String name) throws FactoryException {
+        boolean useCache = getProperties().getProperty("cache", "false").equals("true");
+
+        if (useCache) {
+            WorkflowDescriptor descriptor = (WorkflowDescriptor) cache.get(name);
+
+            if (descriptor != null) {
+                return descriptor;
+            }
+        }
+
+        try {
+            URL url = new URL(name);
+            WorkflowDescriptor descriptor = WorkflowLoader.load(url);
+
+            if (useCache) {
+                cache.put(name, descriptor);
+            }
+
+            return descriptor;
+        } catch (Exception e) {
+            throw new FactoryException("Unable to find workflow " + name, e);
+        }
+    }
+
+    public String[] getWorkflowNames() throws FactoryException {
+        throw new FactoryException("URLWorkflowFactory does not contain a list of workflow names");
+    }
+
+    public boolean saveWorkflow(String name, WorkflowDescriptor descriptor, boolean replace) throws FactoryException {
+        WorkflowDescriptor c = (WorkflowDescriptor) cache.get(name);
+        URL url = null;
+
+        try {
+            url = new URL(name);
+        } catch (MalformedURLException ex) {
+            throw new FactoryException("workflow '" + name + "' is an invalid url:" + ex);
+        }
+
+        boolean useCache = getProperties().getProperty("cache", "false").equals("true");
+
+        if (useCache && (c != null) && !replace) {
+            return false;
+        }
+
+        if (new File(url.getFile()).exists() && !replace) {
+            return false;
+        }
+
+        Writer out = null;
+
+        try {
+            out = new OutputStreamWriter(new FileOutputStream(url.getFile() + ".new"), "utf-8");
+        } catch (FileNotFoundException ex) {
+            throw new FactoryException("Could not create new file to save workflow " + url.getFile());
+        } catch (UnsupportedEncodingException ex) {
+            throw new FactoryException("utf-8 encoding not supported, contact your JVM vendor!");
+        }
+
+        //write it out to a new file, to ensure we don't end up with a messed up file if we're interrupted halfway for some reason
+        PrintWriter writer = new PrintWriter(new BufferedWriter(out));
+        writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+
+        //lets not write the dtd out, since this won't work without our own resolver if behind a firewall or disconnected
+        //writer.println("<!DOCTYPE workflow PUBLIC \"-//OpenSymphony Group//DTD OSWorkflow 2.5//EN\" \"http://www.opensymphony.com/osworkflow/workflow_2_5.dtd\">");
+        descriptor.writeXML(writer, 0);
+        writer.flush();
+        writer.close();
+
+        //now lets rename
+        File original = new File(url.getFile());
+        File backup = new File(url.getFile() + ".bak");
+        File updated = new File(url.getFile() + ".new");
+        boolean isOK = original.renameTo(backup);
+
+        if (!isOK) {
+            log.warn("Unable to backup original workflow file " + original + ", aborting save");
+
+            return false;
+        }
+
+        isOK = updated.renameTo(original);
+
+        if (!isOK) {
+            log.warn("Unable to rename new file " + updated + " to " + original + ", aborting save");
+
+            return false;
+        }
+
+        backup.delete();
+
+        if (useCache) {
+            cache.put(name, descriptor);
+        }
+
+        return true;
+    }
+}

src/java/com/opensymphony/workflow/loader/XMLWorkflowFactory.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow.loader;
+
+import com.opensymphony.workflow.FactoryException;
+import com.opensymphony.workflow.InvalidWorkflowDescriptorException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.w3c.dom.*;
+
+import java.io.*;
+
+import java.net.URL;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.xml.parsers.*;
+
+
+/**
+ * @author Hani Suleiman
+ * Date: May 10, 2002
+ * Time: 11:30:41 AM
+ */
+public class XMLWorkflowFactory extends AbstractWorkflowFactory {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+    private static final Log log = LogFactory.getLog(XMLWorkflowFactory.class);
+
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    protected Map workflows;
+    protected boolean reload;
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public WorkflowDescriptor getWorkflow(String name) throws FactoryException {
+        WorkflowConfig c = (WorkflowConfig) workflows.get(name);
+
+        if (c == null) {
+            throw new FactoryException("Unknown workflow name \"" + name + "\"");
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("getWorkflow " + name + " descriptor=" + c.descriptor);
+        }
+
+        if (c.descriptor != null) {
+            if (reload) {
+                File file = new File(c.url.getFile());
+
+                if (file.exists() && (file.lastModified() > c.lastModified)) {
+                    c.lastModified = file.lastModified();
+                    log.debug("Reloading workflow " + name);
+                    loadWorkflow(c);
+                }
+            }
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("Loading workflow " + name);
+            }
+
+            loadWorkflow(c);
+        }
+
+        return c.descriptor;
+    }
+
+    public String[] getWorkflowNames() {
+        int i = 0;
+        String[] res = new String[workflows.keySet().size()];
+        Iterator it = workflows.keySet().iterator();
+
+        while (it.hasNext()) {
+            res[i++] = (String) it.next();
+        }
+
+        return res;
+    }
+
+    public void initDone() throws FactoryException {
+        reload = getProperties().getProperty("reload", "false").equals("true");
+
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+        InputStream is = null;
+        String name = getProperties().getProperty("resource", "workflows.xml");
+
+        if ((name != null) && (name.indexOf(":/") > -1)) {
+            try {
+                is = new URL(name).openStream();
+            } catch (Exception e) {
+            }
+        }
+
+        if (is == null) {
+            try {
+                is = classLoader.getResourceAsStream(name);
+            } catch (Exception e) {
+            }
+        }
+
+        if (is == null) {
+            try {
+                is = classLoader.getResourceAsStream("/" + name);
+            } catch (Exception e) {
+            }
+        }
+
+        if (is == null) {
+            try {
+                is = classLoader.getResourceAsStream("META-INF/" + name);
+            } catch (Exception e) {
+            }
+        }
+
+        if (is == null) {
+            try {
+                is = classLoader.getResourceAsStream("/META-INF/" + name);
+            } catch (Exception e) {
+            }
+        }
+
+        try {
+            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+            dbf.setNamespaceAware(true);
+
+            DocumentBuilder db = null;
+
+            try {
+                db = dbf.newDocumentBuilder();
+            } catch (ParserConfigurationException e) {
+                log.fatal("Error creating document builder", e);
+            }
+
+            Document doc = db.parse(is);
+
+            Element root = (Element) doc.getElementsByTagName("workflows").item(0);
+            workflows = new HashMap();
+
+            NodeList list = root.getElementsByTagName("workflow");
+
+            for (int i = 0; i < list.getLength(); i++) {
+                Element e = (Element) list.item(i);
+                WorkflowConfig config = new WorkflowConfig(e.getAttribute("type"), e.getAttribute("location"));
+                workflows.put(e.getAttribute("name"), config);
+            }
+        } catch (Exception e) {
+            log.fatal("Error reading xml workflow", e);
+            throw new InvalidWorkflowDescriptorException("Error in workflow config: " + e.getMessage());
+        }
+    }
+
+    public boolean saveWorkflow(String name, WorkflowDescriptor descriptor, boolean replace) throws FactoryException {
+        WorkflowConfig c = (WorkflowConfig) workflows.get(name);
+
+        if ((c != null) && !replace) {
+            return false;
+        }
+
+        if (c == null) {
+            throw new UnsupportedOperationException("Saving of new workflow is not currently supported");
+        }
+
+        Writer out = null;
+
+        try {
+            out = new OutputStreamWriter(new FileOutputStream(c.url.getFile() + ".new"), "utf-8");
+        } catch (FileNotFoundException ex) {
+            throw new FactoryException("Could not create new file to save workflow " + c.url.getFile());
+        } catch (UnsupportedEncodingException ex) {
+            throw new FactoryException("utf-8 encoding not supported, contact your JVM vendor!");
+        }
+
+        writeXML(descriptor, out);
+
+        //write it out to a new file, to ensure we don't end up with a messed up file if we're interrupted halfway for some reason
+        //now lets rename
+        File original = new File(c.url.getFile());
+        File backup = new File(c.url.getFile() + ".bak");
+        File updated = new File(c.url.getFile() + ".new");
+        boolean isOK = !original.exists() || original.renameTo(backup);
+
+        if (!isOK) {
+            log.warn("Unable to backup original workflow file " + original + ", aborting save");
+
+            return false;
+        }
+
+        isOK = updated.renameTo(original);
+
+        if (!isOK) {
+            log.warn("Unable to rename new file " + updated + " to " + original + ", aborting save");
+
+            return false;
+        }
+
+        backup.delete();
+
+        return true;
+    }
+
+    protected void writeXML(WorkflowDescriptor descriptor, Writer out) {
+        PrintWriter writer = new PrintWriter(new BufferedWriter(out));
+        writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+
+        writer.println("<!DOCTYPE workflow PUBLIC \"-//OpenSymphony Group//DTD OSWorkflow 2.5//EN\" \"http://www.opensymphony.com/osworkflow/workflow_2_5.dtd\">");
+        descriptor.writeXML(writer, 0);
+        writer.flush();
+        writer.close();
+    }
+
+    private void loadWorkflow(WorkflowConfig c) throws FactoryException {
+        try {
+            c.descriptor = WorkflowLoader.load(c.url);
+        } catch (Exception e) {
+            throw new FactoryException("Error in workflow descriptor: " + c.url, e);
+        }
+    }
+
+    //~ Inner Classes //////////////////////////////////////////////////////////
+
+    class WorkflowConfig {
+        String location;
+        String type;
+        URL url;
+        WorkflowDescriptor descriptor;
+        long lastModified;
+
+        public WorkflowConfig(String type, String location) {
+            if ("URL".equals(type)) {
+                try {
+                    url = new URL(location);
+
+                    File file = new File(url.getFile());
+
+                    if (file.exists()) {
+                        lastModified = file.lastModified();
+                    }
+                } catch (Exception ex) {
+                }
+            } else if ("file".equals(type)) {
+                try {
+                    File file = new File(location);
+                    url = file.toURL();
+                    lastModified = file.lastModified();
+                } catch (Exception ex) {
+                }
+            } else {
+                url = Thread.currentThread().getContextClassLoader().getResource(location);
+            }
+
+            this.type = type;
+            this.location = location;
+        }
+    }
+}

src/test/auto1.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.5//EN" "http://www.opensymphony.com/osworkflow/workflow_2_5.dtd">
+<workflow>
+  <initial-actions>
+    <action id="1" name="Start Workflow">
+      <pre-functions>
+        <function type="class">
+          <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+        </function>
+      </pre-functions>
+      <results>
+        <unconditional-result old-status="Finished" status="Underway" step="1" owner="${caller}"/>
+      </results>
+    </action>
+  </initial-actions>
+  <steps>
+    <step id="1" name="First Step">
+      <actions>
+        <action id="1" name="The first action">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Queued" step="2"/>
+          </results>
+        </action>
+        <action id="2" name="The second action" auto="true">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Queued" step="2"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="2" name="First Step">
+      <actions>
+        <action id="3" name="The third action">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Queued</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Finished" step="2"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+  </steps>
+</workflow>
+

src/test/auto2.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.5//EN" "http://www.opensymphony.com/osworkflow/workflow_2_5.dtd">
+<workflow>
+  <initial-actions>
+    <action id="1" name="Start Workflow">
+      <pre-functions>
+        <function type="class">
+          <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+        </function>
+      </pre-functions>
+      <results>
+        <unconditional-result old-status="Finished" status="Underway" step="2" owner="${caller}"/>
+      </results>
+    </action>
+  </initial-actions>
+  <steps>
+    <step id="1" name="First Step">
+      <actions>
+        <action id="1" name="The first action" auto="true">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Queued" step="3"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="2" name="Second Step">
+      <actions>
+        <action id="2" name="The second action" auto="true">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Queued" step="4"/>
+          </results>
+        </action>
+        <action id="3" name="The third action" auto="true">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Queued" step="2"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="3" name="Third Step">
+      <actions>
+        <action id="4" name="The fourth action">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Queued</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Finished" step="3"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="4" name="Fourth Step">
+      <actions>
+        <action id="5" name="The fifth action">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Queued</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Finished" step="4"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+  </steps>
+</workflow>
+

src/test/auto3.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.5//EN" "http://www.opensymphony.com/osworkflow/workflow_2_5.dtd">
+<workflow>
+  <initial-actions>
+    <action id="1" name="Start Workflow">
+      <pre-functions>
+        <function type="class">
+          <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+        </function>
+      </pre-functions>
+      <results>
+        <unconditional-result old-status="Finished" status="Underway" step="1" owner="${caller}"/>
+      </results>
+    </action>
+  </initial-actions>
+  <steps>
+    <step id="1" name="First Step">
+      <actions>
+        <action id="1" name="The first action" auto="true">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Queued" step="2"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="2" name="Second Step">
+      <actions>
+        <action id="2" name="The second action" auto="true">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Queued</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Finished" step="3"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="3" name="Third Step">
+      <actions>
+        <action id="3" name="The third action">
+          <results>
+            <unconditional-result old-status="Finished" status="Finished" step="3"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+  </steps>
+</workflow>
+

src/test/auto4.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.5//EN" "http://www.opensymphony.com/osworkflow/workflow_2_5.dtd">
+<workflow>
+  <initial-actions>
+    <action id="1" name="Start Workflow">
+      <pre-functions>
+        <function type="class">
+          <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+        </function>
+      </pre-functions>
+      <results>
+        <unconditional-result old-status="Finished" status="Underway" step="1" owner="${caller}"/>
+      </results>
+    </action>
+  </initial-actions>
+  <steps>
+    <step id="1" name="First Step">
+      <actions>
+        <action id="1" name="The first action" auto="true">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Queued</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Finished" step="2"/>
+          </results>
+        </action>
+        <action id="2" name="The second action" auto="true">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Finished" step="3"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="2" name="Second Step">
+      <actions>
+        <action id="3" name="The third action">
+          <results>
+            <unconditional-result old-status="Finished" status="Finished" step="2"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="3" name="Third Step">
+      <actions>
+        <action id="4" name="The fourth action">
+          <results>
+            <unconditional-result old-status="Finished" status="Finished" step="3"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+  </steps>
+</workflow>
+

src/test/com/opensymphony/workflow/DescriptorLoader.java

+package com.opensymphony.workflow;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+
+import com.opensymphony.workflow.loader.WorkflowDescriptor;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * User: Hani Suleiman (hani@formicary.net)
+ * Date: Feb 12, 2003
+ * Time: 6:49:46 PM
+ */
+public class DescriptorLoader
+{
+  public static WorkflowDescriptor getDescriptor(String url) throws Exception
+  {
+    File file = new File(new URL(url).getFile());
+
+    if (!file.exists()) {
+        throw new IllegalArgumentException("file " + file +" does not exist");
+    }
+
+    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+    dbf.setNamespaceAware(true);
+    dbf.setValidating(true);
+    DocumentBuilder db = dbf.newDocumentBuilder();
+    db.setEntityResolver(new EntityResolver() {
+            public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+              if(systemId==null) return null;
+              URL url = new URL(systemId);
+              String file = url.getFile();
+            if(file!=null && file.indexOf('/')>-1)
+              file = file.substring(file.lastIndexOf('/')+1);
+              if("www.opensymphony.com".equals(url.getHost())) {
+                  InputStream is = getClass().getResourceAsStream("/META-INF/" + file);
+                  if(is==null) is = getClass().getResourceAsStream("/" + file);
+                  if(is!=null) return new InputSource(is);
+                }
+
+                return null;
+            }
+        });
+
+    Document doc = db.parse(new FileInputStream(file));
+    Element root = (Element) doc.getElementsByTagName("workflow").item(0);
+
+    WorkflowDescriptor descriptor = new WorkflowDescriptor(root);
+    return descriptor;
+  }
+}

src/test/com/opensymphony/workflow/SaveDescriptorTestCase.java

+package com.opensymphony.workflow;
+
+import java.io.*;
+
+import junit.framework.TestCase;
+import com.opensymphony.workflow.loader.WorkflowDescriptor;
+
+/**
+ * @author Hani Suleiman (hani@formicary.net)
+ * Date: May 5, 2003
+ * Time: 12:43:08 PM
+ */
+public class SaveDescriptorTestCase extends TestCase
+{
+  public SaveDescriptorTestCase(String s) {
+      super(s);
+  }
+
+  /**
+   *Test if a saved descriptor is identical to the original
+   */
+  public void testSave() throws Exception
+  {
+    WorkflowDescriptor descriptor = DescriptorLoader.getDescriptor("file:saved.xml");
+    StringWriter out = new StringWriter();
+    PrintWriter writer = new PrintWriter(out);
+    writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+    writer.println("<!DOCTYPE workflow PUBLIC \"-//OpenSymphony Group//DTD OSWorkflow 2.5//EN\" \"http://www.opensymphony.com/osworkflow/workflow_2_5.dtd\">");
+    descriptor.writeXML(new PrintWriter(out), 0);
+    int origLength = (int)new File("saved.xml").length();
+    int savedLength = out.toString().length();
+    int diff = Math.abs(origLength - savedLength);
+    assertEquals("Difference between saved and original is " + diff, diff<4, true);
+  }
+}

src/test/double.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.5//EN" "http://www.opensymphony.com/osworkflow/workflow_2_5.dtd">
+
+<workflow>
+  <!-- test for WF-118 -->
+  <initial-actions>
+    <action id="1" name="Start Workflow">
+      <results>
+        <unconditional-result old-status="Done" step="2" status="Expecting Suggestion" owner="${caller}"/>
+      </results>
+    </action>
+  </initial-actions>
+
+  <steps>
+    <step id="2" name="Disagreement">
+      <actions>
+        <action id="3" name="Propose Restaurant">
+          <results>
+            <unconditional-result old-status="Restaurant Proposed" step="3" status="Underway">
+              <pre-functions>
+                <function type="beanshell">
+                  <arg name="script">
+                    com.opensymphony.workflow.DoubleFunctionTestCase test = transientVars.get("test");
+                    test.counter++;
+                  </arg>
+                </function>
+              </pre-functions>
+              <post-functions>
+                <function type="beanshell">
+                  <arg name="script">
+                    com.opensymphony.workflow.DoubleFunctionTestCase test = transientVars.get("test");
+                    test.counter++;
+                  </arg>
+                </function>
+              </post-functions>
+            </unconditional-result>
+          </results>
+        </action>
+      </actions>
+    </step>
+
+    <step id="3" name="Evaluation">
+      <actions>
+        <action id="4" name="Reject Proposal">
+          <results>
+            <unconditional-result old-status="Proposal Rejected" step="2" status="Expecting Suggestion">
+            </unconditional-result>
+          </results>
+        </action>
+      </actions>
+    </step>
+
+  </steps>
+
+</workflow>

src/test/output.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.5//EN" "http://www.opensymphony.com/osworkflow/workflow_2_5.dtd">
+<workflow>
+  <initial-actions>
+    <action id="1" name="Start Workflow">
+      <restrict-to>
+        <conditions type="AND">
+          <condition type="beanshell">
+            <arg name="script"><![CDATA[true]]></arg>
+          </condition>
+          <condition type="class">
+            <arg name="group">foos</arg>
+            <arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
+          </condition>
+        </conditions>
+      </restrict-to>
+      <pre-functions>
+        <function type="class">
+          <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+        </function>
+      </pre-functions>
+      <results>
+        <unconditional-result old-status="Finished" status="Underway" step="1" owner="${caller}"/>
+      </results>
+    </action>
+  </initial-actions>
+  <steps>
+    <step id="1" name="First Draft">
+      <external-permissions>
+        <permission name="permA">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+        </permission>
+      </external-permissions>
+      <actions>
+        <action id="1" name="Finish First Draft">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="beanshell">
+                <arg name="script"><![CDATA[true]]></arg>
+              </condition>
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+            <function type="beanshell">
+              <arg name="script"><![CDATA[
+                String caller = context.getCaller();
+                propertySet.setString("caller", caller);
+                boolean test = true;
+                String yuck = null;
+                String blah = "987654321";
+                System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
+              ]]></arg>
+            </function>
+          </pre-functions>
+          <results>
+            <result old-status="Finished" split="1">
+              <conditions type="AND">
+                <condition type="beanshell">
+                  <arg name="script"><![CDATA[
+                    propertySet.getString("caller").equals("test")
+                  ]]></arg>
+                </condition>
+              </conditions>
+              <post-functions>
+                <function type="beanshell">
+                  <arg name="script"><![CDATA[
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                  ]]></arg>
+                </function>
+              </post-functions>
+            </result>
+            <unconditional-result old-status="Finished" split="2"/>
+          </results>
+          <post-functions>
+            <function type="beanshell">
+              <arg name="script"><![CDATA[
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+              ]]></arg>
+            </function>
+          </post-functions>
+        </action>
+      </actions>
+    </step>
+    <step id="2" name="Edit Doc">
+      <external-permissions>
+        <permission name="permB">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+        </permission>
+      </external-permissions>
+      <actions>
+        <action id="2" name="Sign Up For Editing">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Queued</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="group">bars</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+            <function type="class">
+              <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+            </function>
+          </pre-functions>
+          <results>
+            <unconditional-result old-status="Finished" status="Underway" step="2" owner="${caller}"/>
+          </results>
+        </action>
+        <action id="3" name="Finish Editing">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+            <function type="class">
+              <arg name="stepId">1</arg>
+              <arg name="class.name">com.opensymphony.workflow.util.MostRecentOwner</arg>
+            </function>
+          </pre-functions>
+          <results>
+            <unconditional-result old-status="Finished" status="Underway" step="3" owner="${mostRecentOwner}"/>
+          </results>
+        </action>
+        <action id="4" name="Requeue Editing">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Queued" step="2"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="3" name="Review Doc">
+      <external-permissions>
+        <permission name="permA">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+        </permission>
+      </external-permissions>
+      <actions>
+        <action id="5" name="More Edits">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+Process terminated with exit code -1
+            <function type="class">
+              <arg name="stepId">2</arg>
+              <arg name="class.name">com.opensymphony.workflow.util.MostRecentOwner</arg>
+            </function>
+          </pre-functions>
+          <results>
+            <unconditional-result old-status="Finished" status="Underway" step="2" owner="${mostRecentOwner}"/>
+          </results>
+        </action>
+        <action id="6" name="Peer Review">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Queued" step="4"/>
+          </results>
+        </action>
+        <action id="7" name="Publish Doc">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+            <function type="class">
+              <arg name="stepId">2</arg>
+              <arg name="class.name">com.opensymphony.workflow.util.MostRecentOwner</arg>
+            </function>
+          </pre-functions>
+          <results>
+            <unconditional-result old-status="Finished" status="Underway" step="5" owner="${mostRecentOwner}"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="4" name="Second Review">
+      <external-permissions>
+        <permission name="permC">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+        </permission>
+      </external-permissions>
+      <actions>
+        <action id="8" name="Finish Second Review">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+            <function type="class">
+              <arg name="stepId">3</arg>
+              <arg name="class.name">com.opensymphony.workflow.util.MostRecentOwner</arg>
+            </function>
+          </pre-functions>
+          <results>
+            <unconditional-result old-status="Finished" status="Underway" step="3" owner="${mostRecentOwner}"/>
+          </results>
+        </action>
+        <action id="9" name="Sign Up For Second Review">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Queued</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="group">bazs</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+            <function type="class">
+              <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+            </function>
+          </pre-functions>
+          <results>
+            <unconditional-result old-status="Finished" status="Underway" step="4" owner="${caller}"/>
+          </results>
+        </action>
+        <action id="10" name="Queue Second Review">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Queued" step="4"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="5" name="Publish Doc">
+      <actions>
+        <action id="11" name="Publish Document">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+            <function type="class">
+              <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+            </function>
+          </pre-functions>
+          <results>
+            <unconditional-result old-status="Finished" status="Finished" step="5" owner="${caller}"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="6" name="Foo">
+      <actions>
+        <action id="12" name="Finish Foo">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="stepId">6</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="stepId">6</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" join="1"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="7" name="Bar">
+      <actions>
+        <action id="13" name="Finish Bar">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="stepId">7</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="stepId">7</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Underway" step="8" owner="test"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="8" name="Baz">
+      <actions>
+        <action id="14" name="Finish Baz">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="status">Underway</arg>
+                <arg name="stepId">8</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+              </condition>
+              <condition type="class">
+                <arg name="stepId">8</arg>
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" join="1"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+  </steps>
+  <splits>
+    <split id="1">
+      <unconditional-result old-status="Finished" status="Underway" step="6" owner="test"/>
+      <unconditional-result old-status="Finished" status="Underway" step="7" owner="test"/>
+    </split>
+    <split id="2">
+      <unconditional-result old-status="Finished" status="Queued" step="2"/>
+    </split>
+  </splits>
+  <joins>
+    <join id="1">
+      <conditions type="AND">
+        <condition type="beanshell">
+          <arg name="script"><![CDATA[
+          "Finished".equals(jn.getStep(6).getStatus()) && "Finished".equals(jn.getStep(8).getStatus())
+          ]]></arg>
+        </condition>
+      </conditions>
+      <unconditional-result old-status="Finished" status="Underway" step="2" owner="test"/>
+    </join>
+  </joins>
+</workflow>
+

src/test/propertyset-create.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.5//EN" "http://www.opensymphony.com/osworkflow/workflow_2_5.dtd">
+<workflow>
+	<initial-actions>
+		<action id="1" name="Start Workflow">
+			<restrict-to>
+			</restrict-to>
+			<pre-functions>
+				<function type="beanshell">
+					<arg name="script">
+					  propertySet.setString("myvar","anything"); // put something in propertyset
+					</arg>
+				</function>
+			</pre-functions>
+			<results>
+				<unconditional-result old-status="Finished" status="Underway" step="0"/>
+			</results>
+		</action>
+	</initial-actions>
+
+	<steps>
+    <step id="0" name="Start">
+      <actions>
+        <action id="50" name="Start Workflow" auto="true">
+					<pre-functions>
+						<function type="beanshell">
+							<arg name="script"><![CDATA[
+                List list = transientVars.get("list");
+						    list.add(propertySet.getString("myvar"));
+ 							]]></arg>
+						</function>
+					</pre-functions>
+					<results>
+						<unconditional-result old-status="Finished" status="Underway" step="1"/>
+					</results>
+        </action>
+      </actions>
+    </step>
+
+    <step id="1" name="Step 1">
+      <actions>
+        <action id="101" name="Stop Workflow">
+          <results>
+            <unconditional-result old-status="Finished" status="Finished" step="1"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+	</steps>
+
+</workflow>
+

src/test/saved.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE workflow PUBLIC "-//OpenSymphony Group//DTD OSWorkflow 2.5//EN" "http://www.opensymphony.com/osworkflow/workflow_2_5.dtd">
+<workflow>
+  <initial-actions>
+    <action id="1" name="Start Workflow">
+      <restrict-to>
+        <conditions type="AND">
+          <condition type="beanshell">
+            <arg name="script"><![CDATA[true]]></arg>
+          </condition>
+          <condition type="class">
+            <arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
+            <arg name="group">foos</arg>
+          </condition>
+        </conditions>
+      </restrict-to>
+      <pre-functions>
+        <function type="class">
+          <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+        </function>
+      </pre-functions>
+      <results>
+        <unconditional-result old-status="Finished" status="Underway" step="1" owner="${caller}"/>
+      </results>
+    </action>
+  </initial-actions>
+  <steps>
+    <step id="1" name="First Draft">
+      <external-permissions>
+        <permission name="permA">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+                <arg name="status">Underway</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+        </permission>
+      </external-permissions>
+      <actions>
+        <action id="1" name="Finish First Draft" auto="true">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="beanshell">
+                <arg name="script"><![CDATA[true]]></arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+                <arg name="status">Underway</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+            <function type="beanshell">
+              <arg name="script"><![CDATA[
+                String caller = context.getCaller();
+                propertySet.setString("caller", caller);
+                boolean test = true;
+                String yuck = null;
+                String blah = "987654321";
+                System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
+              ]]></arg>
+            </function>
+          </pre-functions>
+          <results>
+            <result old-status="Finished" split="1">
+              <conditions type="AND">
+                <condition type="beanshell">
+                  <arg name="script"><![CDATA[
+                    propertySet.getString("caller").equals("test")
+                  ]]></arg>
+                </condition>
+              </conditions>
+              <post-functions>
+                <function type="beanshell">
+                  <arg name="script"><![CDATA[
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                    System.out.println("11111111111111");
+                  ]]></arg>
+                </function>
+              </post-functions>
+            </result>
+            <unconditional-result old-status="Finished" split="2"/>
+          </results>
+          <post-functions>
+            <function type="beanshell">
+              <arg name="script"><![CDATA[
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+                System.out.println("22222222222222");
+              ]]></arg>
+            </function>
+          </post-functions>
+        </action>
+      </actions>
+    </step>
+    <step id="2" name="Edit Doc">
+      <external-permissions>
+        <permission name="permB">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+                <arg name="status">Underway</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+        </permission>
+      </external-permissions>
+      <actions>
+        <action id="2" name="Sign Up For Editing">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+                <arg name="status">Queued</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.OSUserGroupCondition</arg>
+                <arg name="group">bars</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+            <function type="class">
+              <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
+            </function>
+          </pre-functions>
+          <results>
+            <unconditional-result old-status="Finished" status="Underway" step="2" owner="${caller}"/>
+          </results>
+        </action>
+        <action id="3" name="Finish Editing">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+                <arg name="status">Underway</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+            <function type="class">
+              <arg name="class.name">com.opensymphony.workflow.util.MostRecentOwner</arg>
+              <arg name="stepId">1</arg>
+            </function>
+          </pre-functions>
+          <results>
+            <unconditional-result old-status="Finished" status="Underway" step="3" owner="${mostRecentOwner}"/>
+          </results>
+        </action>
+        <action id="4" name="Requeue Editing">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+                <arg name="status">Underway</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <results>
+            <unconditional-result old-status="Finished" status="Queued" step="2"/>
+          </results>
+        </action>
+      </actions>
+    </step>
+    <step id="3" name="Review Doc">
+      <external-permissions>
+        <permission name="permA">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+                <arg name="status">Underway</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+        </permission>
+      </external-permissions>
+      <actions>
+        <action id="5" name="More Edits">
+          <restrict-to>
+            <conditions type="AND">
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
+                <arg name="status">Underway</arg>
+              </condition>
+              <condition type="class">
+                <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
+              </condition>
+            </conditions>
+          </restrict-to>
+          <pre-functions>
+            <function type="class">
+              <arg name="class.name">com.opensymphony.workflow.util.MostRecentOwner</arg>
+              <arg name="stepId">2</arg>
+            </function>
+          </pre-functions>
+          <results>
+            <unconditional-result old-status="Finished" status="Underway" step="2" owner="${mostRecentOwner}"/>
+          </results>
+        </action>