Anonymous avatar Anonymous committed c3d13d0

Initial import from SourceForge

Comments (0)

Files changed (49)

src/java/com/opensymphony/workflow/Condition.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow;
+
+import com.opensymphony.module.propertyset.PropertySet;
+
+import java.util.Map;
+
+
+/**
+ * Interface that must be implemented to define a java-based condition in your workflow definition.
+ *
+ * @author <a href="mailto:plightbo@hotmail.com">Patrick Lightbody</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public interface Condition {
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+     * Determines if a condition should signal pass or fail.
+     *
+     * @param transientVars Variables that will not be persisted. These include inputs
+     * given in the {@link Workflow#initialize} and {@link Workflow#doAction} method calls.
+     * There are two special variable names: <b>entry</b> (object type:
+     * {@link com.opensymphony.workflow.spi.WorkflowEntry}) and <b>context</b>
+     * (object type: {@link com.opensymphony.workflow.WorkflowContext}).
+     * Also, any variable set as a {@link com.opensymphony.workflow.Register}), will also be
+     * available in the variable map, no matter what. These variables only last through
+     * the method call that they were invoked in, such as {@link Workflow#initialize}
+     * and {@link Workflow#doAction}.
+     * @param args The properties for this function invocation. Properties are created
+     * from arg nested elements within the xml, an arg element takes in a name attribute
+     * which is the properties key, and the CDATA text contents of the element map to
+     * the property value.
+     * @param ps The persistent variables that are associated with the current
+     * instance of the workflow. Any change made to this will be seen on the next
+     * function call in the workflow lifetime.
+     */
+    public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws WorkflowException;
+}

src/java/com/opensymphony/workflow/FactoryException.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow;
+
+
+/**
+ * @author Hani Suleiman (hani@formicary.net)
+ * Date: Apr 8, 2003
+ * Time: 9:42:15 AM
+ */
+public class FactoryException extends WorkflowException {
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+    public FactoryException() {
+    }
+
+    public FactoryException(String message) {
+        super(message);
+    }
+
+    public FactoryException(Exception cause) {
+        super(cause);
+    }
+
+    public FactoryException(String message, Exception cause) {
+        super(message, cause);
+    }
+}

src/java/com/opensymphony/workflow/FunctionProvider.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow;
+
+import com.opensymphony.module.propertyset.PropertySet;
+
+import java.util.Map;
+
+
+/**
+ * Interface to be implemented by any class that are to be called from within a workflow as a function,
+ * either as a pre-function or a post-function. The args nested elements within the function xml call
+ * will be mapped to the properties parameter.
+ *
+ * @author <a href="mailto:plightbo@hotmail.com">Pat Lightbody</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public interface FunctionProvider {
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+     * Execute this function
+     * @param transientVars Variables that will not be persisted. These include inputs
+     * given in the {@link Workflow#initialize} and {@link Workflow#doAction} method calls.
+     * There are two special variable names: <b>entry</b> (object type:
+     * {@link com.opensymphony.workflow.spi.WorkflowEntry}) and <b>context</b>
+     * (object type: {@link com.opensymphony.workflow.WorkflowContext}).
+     * Also, any variable set as a {@link com.opensymphony.workflow.Register}), will also be
+     * available in the variable map, no matter what. These variables only last through
+     * the method call that they were invoked in, such as {@link Workflow#initialize}
+     * and {@link Workflow#doAction}.
+     * @param args The properties for this function invocation. Properties are created
+     * from arg nested elements within the xml, an arg element takes in a name attribute
+     * which is the properties key, and the CDATA text contents of the element map to
+     * the property value.
+     * @param ps The persistent variables that are associated with the current
+     * instance of the workflow. Any change made to this will be seen on the next
+     * function call in the workflow lifetime.
+     */
+    public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException;
+}

src/java/com/opensymphony/workflow/InvalidInputException.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow;
+
+import java.util.*;
+
+
+/**
+ * Exception to indicate the user input is invalid. Handles both general errors and errors specific to an input.
+ *
+ * @author <a href="mailto:plightbo@hotmail.com">Patrick Lightbody</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class InvalidInputException extends WorkflowException {
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    private List genericErrors = new ArrayList();
+    private Map errors = new HashMap();
+
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+    public InvalidInputException() {
+        super();
+    }
+
+    /**
+     * Creates a new exception using the supplied Object
+     * as a generic base. If the object is an instance of
+     * this exception, all properties are copied to this
+     * exception. If the object is an instance of Map or
+     * String[], an errorName->errorMessage mapping will
+     * be attempted to be extracted. If the object is
+     * something else, it's toString() method will be
+     * called and added as a single generic error.
+     *
+     * @param o the object
+     */
+    public InvalidInputException(Object o) {
+        if (o instanceof InvalidInputException) {
+            InvalidInputException iie = (InvalidInputException) o;
+            errors = iie.errors;
+            genericErrors = iie.genericErrors;
+        } else if (o instanceof Map) {
+            errors = (Map) o;
+        } else if (o instanceof String[]) {
+            String[] stringMap = (String[]) o;
+            int length = stringMap.length;
+            String name = null;
+
+            for (int i = 0; i < length; i++) {
+                if ((i % 2) == 0) {
+                    name = stringMap[i];
+                } else {
+                    addError(name, stringMap[i]);
+                }
+            }
+        } else {
+            addError(o.toString());
+        }
+    }
+
+    /**
+     * Creates a new exception with an associated generic error.
+     *
+     * @param error a generic error message
+     */
+    public InvalidInputException(String error) {
+        super(error);
+        addError(error);
+    }
+
+    /**
+     * Creates a new exception with an error specific to an input.
+     *
+     * @param name the input name that contains the error
+     * @param error an error about the given name
+     */
+    public InvalidInputException(String name, String error) {
+        super();
+        addError(name, error);
+    }
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns a map (String->String) of the input-specific errors.
+     *
+     * @return a map (String->String) of the input-specific errors
+     */
+    public Map getErrors() {
+        return errors;
+    }
+
+    /**
+     * Returns a list (String) of generic errors.
+     *
+     * @return A list (String) of generic errors
+     */
+    public List getGenericErrors() {
+        return genericErrors;
+    }
+
+    /**
+     * Adds a generic error.
+     *
+     * @param error the generic error message
+     */
+    public void addError(String error) {
+        genericErrors.add(error);
+    }
+
+    /**
+     * Adds an input-specific error.
+     *
+     * @param name the name of the input
+     * @param error the error message
+     */
+    public void addError(String name, String error) {
+        errors.put(name, error);
+    }
+
+    public String toString() {
+        return "[InvalidInputException: [Error map: [" + errors + "]] [Error list: [" + genericErrors + "]]";
+    }
+}

src/java/com/opensymphony/workflow/InvalidRoleException.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow;
+
+
+/**
+ * Indicates that the caller did not have enough permissions to perform some action.
+ *
+ * @author <a href="mailto:plightbo@hotmail.com">Patrick Lightbody</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class InvalidRoleException extends WorkflowException {
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+    public InvalidRoleException(String message) {
+        super(message);
+    }
+}

src/java/com/opensymphony/workflow/InvalidWorkflowDescriptorException.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow;
+
+
+/**
+ * Indicates that a Workflow Descriptor was invalid.
+ * Usually this indicates a semantically incorrect XML workflow definition.
+ *
+ * @author <a href="mailto:vorburger@users.sourceforge.net">Michael Vorburger</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class InvalidWorkflowDescriptorException extends FactoryException {
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+    public InvalidWorkflowDescriptorException(String message) {
+        super(message);
+    }
+}

src/java/com/opensymphony/workflow/Register.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow;
+
+import com.opensymphony.workflow.spi.WorkflowEntry;
+
+import java.util.Map;
+
+
+/**
+ * Interface that must be implemented for workflow registers to behave properly.
+ *
+ * @author <a href="mailto:plightbo@hotmail.com">Patrick Lightbody</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public interface Register {
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns the object to bind to the variable map for this workflow instance.
+     *
+     * @param context The current workflow context
+     * @param entry The workflow entry. Note that this might be null, for example in a pre function
+     * before the workflow has been initialised
+     * @param args Map of arguments as set in the workflow descriptor
+
+     * @return the object to bind to the variable map for this workflow instance
+     */
+    public Object registerVariable(WorkflowContext context, WorkflowEntry entry, Map args) throws WorkflowException;
+}

src/java/com/opensymphony/workflow/StoreException.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow;
+
+
+/**
+ * @author Hani Suleiman (hani@formicary.net)
+ * Date: May 10, 2003
+ * Time: 11:29:45 AM
+ */
+public class StoreException extends WorkflowException {
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+    public StoreException(String s) {
+        super(s);
+    }
+
+    public StoreException(String s, Exception ex) {
+        super(s, ex);
+    }
+
+    public StoreException(Exception ex) {
+        super(ex);
+    }
+}

src/java/com/opensymphony/workflow/Validator.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow;
+
+import com.opensymphony.module.propertyset.PropertySet;
+
+import java.util.Map;
+
+
+/**
+ * Interface that must be implemented to define a java-based validator in your workflow definition.
+ *
+ * @author <a href="mailto:plightbo@hotmail.com">Patrick Lightbody</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public interface Validator {
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+     * Validates the user input.
+     *
+     * @param transientVars Variables that will not be persisted. These include inputs
+     * given in the {@link Workflow#initialize} and {@link Workflow#doAction} method calls.
+     * There are two special variable names: <b>entry</b> (object type:
+     * {@link com.opensymphony.workflow.spi.WorkflowEntry}) and <b>context</b>
+     * (object type: {@link com.opensymphony.workflow.WorkflowContext}).
+     * Also, any variable set as a {@link com.opensymphony.workflow.Register}), will also be
+     * available in the variable map, no matter what. These variables only last through
+     * the method call that they were invoked in, such as {@link Workflow#initialize}
+     * and {@link Workflow#doAction}.
+     * @param args The properties for this function invocation. Properties are created
+     * from arg nested elements within the xml, an arg element takes in a name attribute
+     * which is the properties key, and the CDATA text contents of the element map to
+     * the property value.
+     * @param ps The persistent variables that are associated with the current
+     * instance of the workflow. Any change made to this will be seen on the next
+     * function call in the workflow lifetime.
+     * @throws InvalidInputException if the input is deemed to be invalid
+     */
+    public void validate(Map transientVars, Map args, PropertySet ps) throws InvalidInputException, WorkflowException;
+}

src/java/com/opensymphony/workflow/Workflow.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow;
+
+import com.opensymphony.module.propertyset.PropertySet;
+
+import com.opensymphony.workflow.loader.WorkflowDescriptor;
+import com.opensymphony.workflow.query.WorkflowQuery;
+
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * The core workflow interface.
+ *
+ * @author <a href="mailto:plightbo@hotmail.com">Patrick Lightbody</a>
+ */
+public interface Workflow {
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+     * Get an array of possible actions for the specified workflow in the current state that the caller can perform.
+     *
+     * @param id the workflow to get actions for
+     * @return an array of action id's. The collection of action is those that can be performed on the current step
+     */
+    public int[] getAvailableActions(long id) throws WorkflowException;
+
+    /**
+     * Returns a Collection of Step objects that are the current steps of this workflow.
+     *
+     * @param id The workflow ID
+     * @return The steps that the workflow is currently in
+     */
+    public List getCurrentSteps(long id) throws WorkflowException;
+
+    public List getHistorySteps(long id) throws WorkflowException;
+
+    /**
+     * Get the PropertySet for the specified workflow ID
+     * @param id The workflow ID
+     */
+    public PropertySet getPropertySet(long id) throws WorkflowException;
+
+    /**
+     * Get a collection (Strings) of currently defined permissions for the specified workflow/
+     *
+     * @param id the workflow ID
+     * @return A List of permissions specified currently (a permission is a string name)
+     */
+    public List getSecurityPermissions(long id) throws WorkflowException;
+
+    public WorkflowDescriptor getWorkflowDescriptor(String workflowName) throws WorkflowException;
+
+    public String getWorkflowName(long id) throws WorkflowException;
+
+    /**
+     * Check if the calling user has enough permissions to initialise the specified workflow
+     *
+     * @param workflowName The name of the workflow to check
+     * @param initialStep The id of the initial state to check
+     * @return true if the user can successfully call initialize, false otherwise
+     */
+    public boolean canInitialize(String workflowName, int initialStep) throws WorkflowException;
+
+    /**
+     * Perform an action on the specified workflow
+     *
+     * @param id The workflow id to perform the action on
+     * @param actionId The action id to perform (action id's are listed in the workflow descriptor)
+     * @param inputs
+     * @throws InvalidInputException
+     */
+    public void doAction(long id, int actionId, Map inputs) throws InvalidInputException, WorkflowException;
+
+    /**
+     * Executes a special trigger-function using the context of the given workflow instance id.
+     *
+     * @param id the workflow instance id
+     * @param triggerId the id of the speciail trigger-function
+     */
+    public void executeTriggerFunction(long id, int triggerId) throws WorkflowException;
+
+    /**
+    * Initializes a workflow so that it can begin processing. A workflow must be initialized before it can
+    * begin any sort of activity. It can only be initialized once.
+    *
+    * @param workflowName the workflow instance id
+    * @param initialAction the initial step to start the workflow
+    * @param inputs the inputs entered by the end-user
+    * @throws InvalidRoleException if the user can't start this function
+    * @throws InvalidInputException if the input the user gave is invalid
+    */
+    public long initialize(String workflowName, int initialAction, Map inputs) throws InvalidRoleException, InvalidInputException, WorkflowException;
+
+    public List query(WorkflowQuery query) throws WorkflowException;
+}

src/java/com/opensymphony/workflow/WorkflowException.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+
+/**
+ * @author Hani Suleiman (hani@formicary.net)
+ * Date: May 10, 2003
+ * Time: 11:26:07 AM
+ */
+public class WorkflowException extends Exception {
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    private Exception rootCause;
+
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+    public WorkflowException() {
+    }
+
+    public WorkflowException(String s) {
+        super(s);
+    }
+
+    public WorkflowException(String s, Exception rootCause) {
+        super(s);
+        this.rootCause = rootCause;
+    }
+
+    public WorkflowException(Exception rootCause) {
+        this.rootCause = rootCause;
+    }
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public String getMessage() {
+        StringBuffer sb = new StringBuffer();
+        String msg = super.getMessage();
+
+        if (msg != null) {
+            sb.append(msg);
+
+            if (rootCause != null) {
+                sb.append(": ");
+            }
+        }
+
+        if (rootCause != null) {
+            sb.append("root cause: " + rootCause.getMessage());
+        }
+
+        return sb.toString();
+    }
+
+    public Exception getRootCause() {
+        return rootCause;
+    }
+
+    public void printStackTrace() {
+        super.printStackTrace();
+
+        if (rootCause != null) {
+            synchronized (System.err) {
+                System.err.println("\nRoot cause:");
+                rootCause.printStackTrace();
+            }
+        }
+    }
+
+    public void printStackTrace(PrintStream s) {
+        super.printStackTrace(s);
+
+        if (rootCause != null) {
+            synchronized (s) {
+                s.println("\nRoot cause:");
+                rootCause.printStackTrace(s);
+            }
+        }
+    }
+
+    public void printStackTrace(PrintWriter s) {
+        super.printStackTrace(s);
+
+        if (rootCause != null) {
+            synchronized (s) {
+                s.println("\nRoot cause:");
+                rootCause.printStackTrace(s);
+            }
+        }
+    }
+}

src/java/com/opensymphony/workflow/basic/BasicWorkflow.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+/*
+ * Created by IntelliJ IDEA.
+ * User: plightbo
+ * Date: Apr 29, 2002
+ * Time: 11:12:05 PM
+ */
+package com.opensymphony.workflow.basic;
+
+import com.opensymphony.workflow.AbstractWorkflow;
+import com.opensymphony.workflow.FactoryException;
+import com.opensymphony.workflow.config.ConfigLoader;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+import java.net.URL;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision: 1.1.1.1 $
+ */
+public class BasicWorkflow extends AbstractWorkflow {
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+    public BasicWorkflow(String caller) {
+        super.context = new BasicWorkflowContext(caller);
+    }
+}

src/java/com/opensymphony/workflow/ejb/WorkflowEJB.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow.ejb;
+
+import com.opensymphony.workflow.*;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.Map;
+
+import javax.ejb.*;
+
+
+/**
+ * @ejb.bean
+ *  type="Stateless"
+ *  name="Workflow"
+ *  view-type="remote"
+ *  transaction-type="Container"
+ *
+ * @ejb.ejb-ref
+ *  ejb-name="HistoryStepPrev"
+ *  view-type="local"
+ *
+ * @ejb.ejb-ref
+ *  ejb-name="CurrentStepPrev"
+ *  view-type="local"
+ *
+ * @ejb.ejb-ref
+ *  ejb-name="CurrentStep"
+ *  view-type="local"
+ *
+ * @ejb.ejb-ref
+ *  ejb-name="HistoryStep"
+ *  view-type="local"
+ *
+ * @ejb.permission unchecked="true"
+ * @ejb.transaction type="Supports"
+ *
+ * @author <a href="mailto:plightbo@hotmail.com">Pat Lightbody</a>
+ * @author <a href="mailto:hani@formicary.net">Hani Suleiman</a>
+ * @version $Revision: 1.1.1.1 $
+ */
+public class WorkflowEJB extends AbstractWorkflow implements SessionBean {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+    private static final Log log = LogFactory.getLog(WorkflowEJB.class);
+
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    private SessionContext sessionContext;
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public void setSessionContext(SessionContext context) {
+        this.sessionContext = context;
+        super.context = new EJBWorkflowContext(context);
+    }
+
+    /**
+     * @ejb.interface-method
+     * @ejb.transaction type="Required"
+     */
+    public void doAction(long id, int actionId, Map inputs) throws WorkflowException {
+        super.doAction(id, actionId, inputs);
+    }
+
+    public void ejbActivate() {
+    }
+
+    public void ejbCreate() throws CreateException {
+        try {
+            loadConfig();
+        } catch (WorkflowException ex) {
+            log.error("Error loading config");
+            throw new CreateException(ex.getMessage());
+        }
+    }
+
+    public void ejbPassivate() {
+    }
+
+    public void ejbPostCreate() throws CreateException {
+    }
+
+    public void ejbRemove() {
+    }
+
+    /**
+     * @ejb.interface-method
+     * @ejb.transaction type="Required"
+     */
+    public void executeTriggerFunction(long id, int triggerId) throws WorkflowException {
+        super.executeTriggerFunction(id, triggerId);
+    }
+
+    /**
+     * @ejb.interface-method
+     * @ejb.transaction type="Required"
+     */
+    public long initialize(String workflowName, int initialAction, Map inputs) throws InvalidRoleException, InvalidInputException, StoreException, WorkflowException {
+        return super.initialize(workflowName, initialAction, inputs);
+    }
+}

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

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow.loader;
+
+import com.opensymphony.workflow.FactoryException;
+
+import java.util.List;
+import java.util.Properties;
+
+
+/**
+ * @author Hani Suleiman
+ * Date: May 10, 2002
+ * Time: 11:17:06 AM
+ */
+public abstract class AbstractWorkflowFactory {
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    protected Properties properties = new Properties();
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public Properties getProperties() {
+        return properties;
+    }
+
+    public final void init(Properties p) throws FactoryException {
+        this.properties = p;
+    }
+
+    public abstract WorkflowDescriptor getWorkflow(String name) throws FactoryException;
+
+    public abstract String[] getWorkflowNames() throws FactoryException;
+
+    public abstract boolean saveWorkflow(String name, WorkflowDescriptor descriptor, boolean replace) throws FactoryException;
+
+    public void initDone() throws FactoryException {
+    }
+}

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

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow.loader;
+
+import com.opensymphony.workflow.InvalidWorkflowDescriptorException;
+import com.opensymphony.workflow.util.Validatable;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision: 1.1.1.1 $
+ */
+public class ValidationHelper {
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public static void validate(Collection c) throws InvalidWorkflowDescriptorException {
+        Iterator iter = c.iterator();
+
+        while (iter.hasNext()) {
+            Object o = iter.next();
+
+            if (o instanceof Validatable) {
+                ((Validatable) o).validate();
+            }
+        }
+    }
+}

src/java/com/opensymphony/workflow/ofbiz/OfbizWorkflow.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+/*
+ * Created by IntelliJ IDEA.
+ * User: plightbo
+ * Date: Apr 29, 2002
+ * Time: 11:22:48 PM
+ */
+package com.opensymphony.workflow.ofbiz;
+
+import com.opensymphony.workflow.*;
+
+import org.ofbiz.core.entity.GenericTransactionException;
+import org.ofbiz.core.entity.TransactionUtil;
+
+import java.util.Map;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision: 1.1.1.1 $
+ */
+public class OfbizWorkflow extends AbstractWorkflow {
+    //~ Constructors ///////////////////////////////////////////////////////////
+
+    public OfbizWorkflow(String caller) throws FactoryException {
+        super.context = new OfbizWorkflowContext(caller);
+        loadConfig();
+    }
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public void doAction(long id, int actionId, Map inputs) throws WorkflowException {
+        try {
+            TransactionUtil.begin();
+        } catch (GenericTransactionException e) {
+            throw new WorkflowException(e);
+        }
+
+        super.doAction(id, actionId, inputs);
+
+        try {
+            TransactionUtil.commit();
+        } catch (GenericTransactionException e) {
+            throw new WorkflowException(e);
+        }
+    }
+
+    public long initialize(String workflowName, int initialState, Map inputs) throws WorkflowException {
+        try {
+            TransactionUtil.begin();
+        } catch (GenericTransactionException e) {
+            throw new WorkflowException(e);
+        }
+
+        long id = super.initialize(workflowName, initialState, inputs);
+
+        try {
+            TransactionUtil.commit();
+
+            return id;
+        } catch (GenericTransactionException e) {
+            throw new WorkflowException(e);
+        }
+    }
+}

src/java/com/opensymphony/workflow/soap/BasicSOAPWorkflow.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+/*
+ * Created by IntelliJ IDEA.
+ * User: plightbo
+ * Date: May 22, 2002
+ * Time: 12:04:32 PM
+ */
+package com.opensymphony.workflow.soap;
+
+import com.opensymphony.module.propertyset.PropertySet;
+
+import com.opensymphony.workflow.*;
+import com.opensymphony.workflow.basic.BasicWorkflow;
+import com.opensymphony.workflow.loader.WorkflowDescriptor;
+import com.opensymphony.workflow.query.WorkflowQuery;
+
+import electric.util.Context;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision: 1.1.1.1 $
+ */
+public class BasicSOAPWorkflow implements Workflow {
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public int[] getAvailableActions(long id) throws WorkflowException {
+        return new BasicWorkflow(getRemoteUser()).getAvailableActions(id);
+    }
+
+    public List getCurrentSteps(long id) throws StoreException {
+        return new BasicWorkflow(getRemoteUser()).getCurrentSteps(id);
+    }
+
+    public List getHistorySteps(long id) throws StoreException {
+        return new BasicWorkflow(getRemoteUser()).getHistorySteps(id);
+    }
+
+    public PropertySet getPropertySet(long id) throws StoreException {
+        return new BasicWorkflow(getRemoteUser()).getPropertySet(id);
+    }
+
+    public List getSecurityPermissions(long id) throws WorkflowException {
+        return new BasicWorkflow(getRemoteUser()).getSecurityPermissions(id);
+    }
+
+    public WorkflowDescriptor getWorkflowDescriptor(String workflowName) throws FactoryException {
+        return new BasicWorkflow(getRemoteUser()).getWorkflowDescriptor(workflowName);
+    }
+
+    public String getWorkflowName(long id) throws StoreException {
+        return new BasicWorkflow(getRemoteUser()).getWorkflowName(id);
+    }
+
+    public boolean canInitialize(String workflowName, int initialState) throws WorkflowException {
+        return new BasicWorkflow(getRemoteUser()).canInitialize(workflowName, initialState);
+    }
+
+    public void doAction(long id, int actionId, Map inputs) throws WorkflowException {
+        new BasicWorkflow(getRemoteUser()).doAction(id, actionId, inputs);
+    }
+
+    public void executeTriggerFunction(long id, int triggerId) throws WorkflowException {
+        new BasicWorkflow(getRemoteUser()).executeTriggerFunction(id, triggerId);
+    }
+
+    public long initialize(String workflowName, int initialState, Map inputs) throws WorkflowException {
+        return new BasicWorkflow(getRemoteUser()).initialize(workflowName, initialState, inputs);
+    }
+
+    public List query(WorkflowQuery query) throws StoreException {
+        return new BasicWorkflow(getRemoteUser()).query(query);
+    }
+
+    private String getRemoteUser() {
+        HttpServletRequest request = (HttpServletRequest) Context.thread().getProperty("httpRequest");
+
+        return request.getRemoteUser();
+    }
+}

src/java/com/opensymphony/workflow/soap/OfbizSOAPWorkflow.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+/*
+ * Created by IntelliJ IDEA.
+ * User: plightbo
+ * Date: May 22, 2002
+ * Time: 2:02:10 PM
+ */
+package com.opensymphony.workflow.soap;
+
+import com.opensymphony.module.propertyset.PropertySet;
+
+import com.opensymphony.workflow.*;
+import com.opensymphony.workflow.loader.WorkflowDescriptor;
+import com.opensymphony.workflow.ofbiz.OfbizWorkflow;
+import com.opensymphony.workflow.query.WorkflowQuery;
+
+import electric.util.Context;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision: 1.1.1.1 $
+ */
+public class OfbizSOAPWorkflow implements Workflow {
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public int[] getAvailableActions(long id) throws WorkflowException {
+        return new OfbizWorkflow(getRemoteUser()).getAvailableActions(id);
+    }
+
+    public List getCurrentSteps(long id) throws StoreException, FactoryException {
+        return new OfbizWorkflow(getRemoteUser()).getCurrentSteps(id);
+    }
+
+    public List getHistorySteps(long id) throws StoreException, FactoryException {
+        return new OfbizWorkflow(getRemoteUser()).getHistorySteps(id);
+    }
+
+    public PropertySet getPropertySet(long id) throws StoreException, FactoryException {
+        return new OfbizWorkflow(getRemoteUser()).getPropertySet(id);
+    }
+
+    public List getSecurityPermissions(long id) throws WorkflowException {
+        return new OfbizWorkflow(getRemoteUser()).getSecurityPermissions(id);
+    }
+
+    public WorkflowDescriptor getWorkflowDescriptor(String workflowName) throws FactoryException {
+        return new OfbizWorkflow(getRemoteUser()).getWorkflowDescriptor(workflowName);
+    }
+
+    public String getWorkflowName(long id) throws StoreException, FactoryException {
+        return new OfbizWorkflow(getRemoteUser()).getWorkflowName(id);
+    }
+
+    public boolean canInitialize(String workflowName, int initialState) throws WorkflowException {
+        return new OfbizWorkflow(getRemoteUser()).canInitialize(workflowName, initialState);
+    }
+
+    public void doAction(long id, int actionId, Map inputs) throws WorkflowException, StoreException {
+        new OfbizWorkflow(getRemoteUser()).doAction(id, actionId, inputs);
+    }
+
+    public void executeTriggerFunction(long id, int triggerId) throws WorkflowException {
+        new OfbizWorkflow(getRemoteUser()).executeTriggerFunction(id, triggerId);
+    }
+
+    public long initialize(String workflowName, int initialState, Map inputs) throws WorkflowException {
+        return new OfbizWorkflow(getRemoteUser()).initialize(workflowName, initialState, inputs);
+    }
+
+    public List query(WorkflowQuery query) throws StoreException, FactoryException {
+        return new OfbizWorkflow(getRemoteUser()).query(query);
+    }
+
+    private String getRemoteUser() {
+        HttpServletRequest request = (HttpServletRequest) Context.thread().getProperty("httpRequest");
+
+        return request.getRemoteUser();
+    }
+}

src/java/com/opensymphony/workflow/spi/StoreFactory.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow.spi;
+
+import com.opensymphony.workflow.StoreException;
+import com.opensymphony.workflow.WorkflowContext;
+import com.opensymphony.workflow.config.ConfigLoader;
+import com.opensymphony.workflow.spi.memory.MemoryWorkflowStore;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * Class that is used to intialize a workflow store and
+ * save the single instance of it for future use.
+ *
+ * @author <a href="mailto:plightbo@hotmail.com">Pat Lightbody</a>
+ */
+public class StoreFactory {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+    private static final Log log = LogFactory.getLog(StoreFactory.class);
+    private static WorkflowStore store;
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public static WorkflowStore getPersistence(WorkflowContext context) throws StoreException {
+        if (store == null) {
+            String clazz = ConfigLoader.persistence;
+
+            log.info("Initializing WorkflowStore: " + clazz);
+
+            try {
+                store = (WorkflowStore) Class.forName(clazz).newInstance();
+            } catch (Exception ex) {
+                throw new StoreException("Error creating store", ex);
+            }
+
+            store.init(ConfigLoader.persistenceArgs);
+        }
+
+        return store;
+    }
+}

src/java/com/opensymphony/workflow/spi/WorkflowStore.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow.spi;
+
+import com.opensymphony.module.propertyset.PropertySet;
+
+import com.opensymphony.workflow.StoreException;
+import com.opensymphony.workflow.query.WorkflowQuery;
+
+import java.util.*;
+
+
+/**
+ * Interface for pluggable workflow stores configured in osworkflow.xml.
+ * Only one instance of a workflow store is ever created, meaning that
+ * if your persistence connections (such as java.sql.Connection) time out,
+ * it would be un-wise to use just one Connection for the entire object.
+ *
+ * @author <a href="mailto:plightbo@hotmail.com">Pat Lightbody</a>
+ */
+public interface WorkflowStore {
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns a PropertySet that is associated with this workflow instance ID.
+     *
+     * @param entryId the ID of the workflow instance
+     * @return a property set unique to this entry ID
+     */
+    public PropertySet getPropertySet(long entryId) throws StoreException;
+
+    /**
+     * Persists a step with the given parameters.
+     *
+     * @param entryId the ID of the workflow instance
+     * @param stepId the ID of the workflow step associated with this new
+     *               Step (not to be confused with the step primary key)
+     * @param owner the owner of the step
+     * @param startDate the start date of the step
+     * @param status the status of the step
+     * @param previousIds the previous step IDs
+     * @return a representation of the workflow step persisted
+     */
+    public Step createCurrentStep(long entryId, int stepId, String owner, Date startDate, Date dueDate, String status, long[] previousIds) throws StoreException;
+
+    /**
+     * Persists a new workflow entry that has <b>not been initialized</b>.
+     *
+     * @param workflowName the workflow name that this entry is an instance of
+     * @return a representation of the workflow instance persisted
+     */
+    public WorkflowEntry createEntry(String workflowName) throws StoreException;
+
+    /**
+     * Returns a list of all current steps for the given workflow instance ID.
+     *
+     * @param entryId the ID of the workflow instance
+     * @return a List of Steps
+     * @see com.opensymphony.workflow.spi.Step
+     */
+    public List findCurrentSteps(long entryId) throws StoreException;
+
+    /**
+     * Pulls up the workflow entry data for the entry ID given.
+     *
+     * @param entryId the ID of the workflow instance
+     * @return a representation of the workflow instance persisted
+     */
+    public WorkflowEntry findEntry(long entryId) throws StoreException;
+
+    /**
+     * Returns a list of all steps that are finished for the given workflow instance ID.
+     *
+     * @param entryId the ID of the workflow instance
+     * @return a List of Steps
+     * @see com.opensymphony.workflow.spi.Step
+     */
+    public List findHistorySteps(long entryId) throws StoreException;
+
+    /**
+     * Called once when the store is first created.
+     *
+     * @param props properties set in osworkflow.xml
+     */
+    public void init(Map props) throws StoreException;
+
+    public Step markFinished(Step step, int actionId, Date finishDate, String status, String caller) throws StoreException;
+
+    /**
+     * Called when a step is finished and can be moved to workflow history.
+     *
+     * @param step the step to be moved to workflow history
+     */
+    public void moveToHistory(Step step) throws StoreException;
+
+    public List query(WorkflowQuery query) throws StoreException;
+}

src/java/com/opensymphony/workflow/spi/ejb/WorkflowStoreSessionEJB.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow.spi.ejb;
+
+import com.opensymphony.workflow.StoreException;
+import com.opensymphony.workflow.query.WorkflowQuery;
+import com.opensymphony.workflow.spi.SimpleStep;
+import com.opensymphony.workflow.spi.SimpleWorkflowEntry;
+import com.opensymphony.workflow.spi.Step;
+import com.opensymphony.workflow.spi.WorkflowEntry;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.sql.Timestamp;
+
+import java.util.*;
+
+import javax.ejb.SessionBean;
+
+
+/**
+ * @ejb.bean
+ *  type="Stateless"
+ *  name="WorkflowStore"
+ *  view-type="remote"
+ *  transaction-type="Container"
+ *
+ * @ejb.ejb-ref
+ *  ejb-name="HistoryStepPrev"
+ *  view-type="local"
+ *
+ * @ejb.ejb-ref
+ *  ejb-name="CurrentStepPrev"
+ *  view-type="local"
+ *
+ * @ejb.ejb-ref
+ *  ejb-name="CurrentStep"
+ *  view-type="local"
+ *
+ * @ejb.ejb-ref
+ *  ejb-name="HistoryStep"
+ *  view-type="local"
+ *
+ * @ejb.ejb-ref
+ *  ejb-name="WorkflowEntry"
+ *  view-type="local"
+ *
+ * @ejb.permission unchecked="true"
+ * @ejb.transaction type="Supports"
+ *
+ * @author <a href="mailto:hani@formicary.net">Hani Suleiman</a>
+ * @version $Revision: 1.1.1.1 $
+ *
+ * Date: Apr 7, 2003
+ * Time: 10:57:28 PM
+ */
+public abstract class WorkflowStoreSessionEJB implements SessionBean {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+    private static final Log log = LogFactory.getLog(WorkflowStoreSessionEJB.class);
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+     * @ejb.interface-method
+     */
+    public Step createCurrentStep(long entryId, int stepId, String owner, Date startDate, Date dueDate, String status, long[] previousIds) throws StoreException {
+        try {
+            WorkflowEntryLocal entry = WorkflowEntryHomeFactory.getLocalHome().findByPrimaryKey(new Long(entryId));
+            entry.setInitialized(1);
+
+            Timestamp realDueDate = null;
+
+            if (dueDate != null) {
+                realDueDate = new Timestamp(dueDate.getTime());
+            }
+
+            CurrentStepLocal step = CurrentStepHomeFactory.getLocalHome().create(entryId, stepId, owner, new Timestamp(startDate.getTime()), realDueDate, status);
+
+            long id = step.getId().longValue();
+
+            for (int i = 0; i < previousIds.length; i++) {
+                long previousId = previousIds[i];
+                CurrentStepPrevHomeFactory.getLocalHome().create(id, previousId);
+            }
+
+            return new SimpleStep(id, entryId, stepId, 0, owner, startDate, dueDate, null, status, previousIds, null);
+        } catch (Exception e) {
+            throw new StoreException("Could not create new current step for workflow #" + entryId + " step #" + stepId + ":" + e, e);
+        }
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public WorkflowEntry createEntry(String workflowName) throws StoreException {
+        try {
+            WorkflowEntryLocal entry = WorkflowEntryHomeFactory.getLocalHome().create(workflowName);
+
+            return new SimpleWorkflowEntry(entry.getId().longValue(), entry.getWorkflowName(), false);
+        } catch (Exception e) {
+            throw new StoreException("Could not create new workflow entry", e);
+        }
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public List findCurrentSteps(long entryId) throws StoreException {
+        try {
+            Collection results = CurrentStepHomeFactory.getLocalHome().findByEntryId(entryId);
+            TreeSet set = new TreeSet(new StepComparator());
+
+            for (Iterator iterator = results.iterator(); iterator.hasNext();) {
+                CurrentStepLocal stepLocal = (CurrentStepLocal) iterator.next();
+
+                long id = stepLocal.getId().longValue();
+                Collection prevSteps = CurrentStepPrevHomeFactory.getLocalHome().findByStepId(id);
+                long[] prevIds = new long[prevSteps.size()];
+                int i = 0;
+
+                for (Iterator iterator2 = prevSteps.iterator();
+                        iterator2.hasNext();) {
+                    CurrentStepPrevLocal stepPrev = (CurrentStepPrevLocal) iterator2.next();
+                    prevIds[i] = stepPrev.getPreviousId().longValue();
+                    i++;
+                }
+
+                SimpleStep step = new SimpleStep(id, stepLocal.getEntryId(), stepLocal.getStepId(), stepLocal.getActionId(), stepLocal.getOwner(), stepLocal.getStartDate(), stepLocal.getDueDate(), stepLocal.getFinishDate(), stepLocal.getStatus(), prevIds, stepLocal.getCaller());
+                set.add(step);
+            }
+
+            return new ArrayList(set);
+        } catch (Exception e) {
+            throw new StoreException("Error in findCurrentSteps", e);
+        }
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public WorkflowEntry findEntry(long entryId) throws StoreException {
+        try {
+            WorkflowEntryLocal entry = WorkflowEntryHomeFactory.getLocalHome().findByPrimaryKey(new Long(entryId));
+
+            return new SimpleWorkflowEntry(entry.getId().longValue(), entry.getWorkflowName(), entry.getInitialized() == 1);
+        } catch (Exception e) {
+            throw new StoreException("Could not find workflow entry #" + entryId, e);
+        }
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public List findHistorySteps(long entryId) throws StoreException {
+        try {
+            Collection results = HistoryStepHomeFactory.getLocalHome().findByEntryId(entryId);
+            TreeSet set = new TreeSet(new StepComparator());
+
+            for (Iterator iterator = results.iterator(); iterator.hasNext();) {
+                HistoryStepLocal stepRemote = (HistoryStepLocal) iterator.next();
+
+                long id = stepRemote.getId().longValue();
+                Collection prevSteps = HistoryStepPrevHomeFactory.getLocalHome().findByStepId(id);
+                long[] prevIds = new long[prevSteps.size()];
+                int i = 0;
+
+                for (Iterator iterator2 = prevSteps.iterator();
+                        iterator2.hasNext();) {
+                    HistoryStepPrevLocal stepPrev = (HistoryStepPrevLocal) iterator2.next();
+                    prevIds[i] = stepPrev.getPreviousId().longValue();
+                    i++;
+                }
+
+                SimpleStep step = new SimpleStep(stepRemote.getId().longValue(), stepRemote.getEntryId(), stepRemote.getStepId(), stepRemote.getActionId(), stepRemote.getOwner(), stepRemote.getStartDate(), stepRemote.getDueDate(), stepRemote.getFinishDate(), stepRemote.getStatus(), prevIds, stepRemote.getCaller());
+                set.add(step);
+            }
+
+            return new ArrayList(set);
+        } catch (Exception e) {
+            throw new StoreException("Could not find history steps for entry #" + entryId, e);
+        }
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public Step markFinished(Step step, int actionId, Date finishDate, String status, String caller) throws StoreException {
+        try {
+            CurrentStepLocal currentStep = CurrentStepHomeFactory.getLocalHome().findByPrimaryKey(new Long(step.getId()));
+            currentStep.setActionId(actionId);
+            currentStep.setFinishDate(new Timestamp(finishDate.getTime()));
+            currentStep.setStatus(status);
+            currentStep.setCaller(caller);
+
+            SimpleStep theStep = (SimpleStep) step;
+            theStep.setActionId(actionId);
+            theStep.setFinishDate(finishDate);
+            theStep.setStatus(status);
+            theStep.setCaller(caller);
+
+            return theStep;
+        } catch (Exception e) {
+            throw new StoreException("Could not mark step finished for #" + step.getEntryId(), e);
+        }
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public void moveToHistory(Step step) throws StoreException {
+        Long id = new Long(step.getId());
+
+        try {
+            CurrentStepLocal currentStep = CurrentStepHomeFactory.getLocalHome().findByPrimaryKey(id);
+            long[] previousIds = step.getPreviousStepIds();
+            Timestamp realDueDate = null;
+
+            if (step.getDueDate() != null) {
+                realDueDate = new Timestamp(step.getDueDate().getTime());
+            }
+
+            Timestamp finishedDate = null;
+
+            if (step.getFinishDate() != null) {
+                finishedDate = new Timestamp(step.getFinishDate().getTime());
+            }
+
+            HistoryStepLocalHome historyHome = HistoryStepHomeFactory.getLocalHome();
+            historyHome.create(id.longValue(), step.getEntryId(), step.getStepId(), step.getActionId(), step.getOwner(), new Timestamp(step.getStartDate().getTime()), realDueDate, finishedDate, step.getStatus(), step.getCaller());
+
+            for (int i = 0; i < previousIds.length; i++) {
+                long previousId = previousIds[i];
+                HistoryStepPrevHomeFactory.getLocalHome().create(id.longValue(), previousId);
+            }
+
+            Collection oldPrevSteps = CurrentStepPrevHomeFactory.getLocalHome().findByStepId(id.longValue());
+
+            for (Iterator iterator = oldPrevSteps.iterator();
+                    iterator.hasNext();) {
+                CurrentStepPrevLocal oldPrevStep = (CurrentStepPrevLocal) iterator.next();
+                oldPrevStep.remove();
+            }
+
+            currentStep.remove();
+        } catch (Exception e) {
+            throw new StoreException("Could not move step to history for workflow #" + id, e);
+        }
+    }
+
+    /**
+     * @ejb.interface-method
+     */
+    public List query(WorkflowQuery query) throws StoreException {
+        // not implemented
+        throw new StoreException("EJB store does not support queries");
+    }
+}

src/java/com/opensymphony/workflow/spi/ofbiz/OfbizWorkflowStore.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow.spi.ofbiz;
+
+import com.opensymphony.module.propertyset.PropertySet;
+import com.opensymphony.module.propertyset.PropertySetManager;
+
+import com.opensymphony.workflow.StoreException;
+import com.opensymphony.workflow.query.WorkflowQuery;
+import com.opensymphony.workflow.spi.*;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.ofbiz.core.entity.*;
+import org.ofbiz.core.util.UtilMisc;
+
+import java.sql.Timestamp;
+
+import java.util.*;
+
+
+/**
+ * OpenForBusiness Entity Engine implemenation.
+ * <p>
+ *
+ * Has one <b>optional</b> property that can be provided:
+ * <ul>
+ *  <li>delegator - the delegator name, defaults to "default"</li>
+ * </ul>
+ *
+ * @author <a href="mailto:plightbo@hotmail.com">Pat Lightbody</a>
+ */
+public class OfbizWorkflowStore implements WorkflowStore {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+    private static final Log log = LogFactory.getLog(OfbizWorkflowStore.class);
+
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    private GenericDelegator gd;
+    private String delegatorName;
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public PropertySet getPropertySet(long entryId) {
+        HashMap args = new HashMap(2);
+        args.put("entityId", new Long(entryId));
+        args.put("entityName", "WorkflowEntry");
+
+        return PropertySetManager.getInstance("ofbiz", args);
+    }
+
+    public Step createCurrentStep(long entryId, int stepId, String owner, Date startDate, Date dueDate, String status, long[] previousIds) throws StoreException {
+        try {
+            Long id = gd.getNextSeqId("OSCurrentStep");
+            HashMap valueMap = new HashMap();
+            valueMap.put("id", id);
+            valueMap.put("entryId", new Long(entryId));
+            valueMap.put("actionId", new Integer(0));
+            valueMap.put("stepId", new Integer(stepId));
+            valueMap.put("owner", owner);
+            valueMap.put("startDate", new Timestamp(startDate.getTime()));
+
+            Timestamp realDueDate = null;
+
+            if (dueDate != null) {
+                realDueDate = new Timestamp(dueDate.getTime());
+            }
+
+            valueMap.put("dueDate", realDueDate);
+            valueMap.put("finishDate", null);
+            valueMap.put("status", status);
+
+            GenericValue gv = gd.create("OSCurrentStep", valueMap);
+            ArrayList storeList = new ArrayList();
+            storeList.add(gv);
+
+            if (previousIds != null) {
+                if (!((previousIds.length == 1) && (previousIds[0] == 0))) {
+                    for (int i = 0; i < previousIds.length; i++) {
+                        long previousId = previousIds[i];
+                        GenericValue prevGv = gd.create("OSCurrentStepPrev", UtilMisc.toMap("id", id, "previousId", new Long(previousId)));
+                        storeList.add(prevGv);
+                    }
+                }
+            }
+
+            gd.storeAll(storeList);
+
+            return new SimpleStep(id.longValue(), entryId, stepId, 0, owner, startDate, dueDate, null, status, previousIds, null);
+        } catch (GenericEntityException e) {
+            throw new StoreException("Could not create new current step for #" + entryId, e);
+        }
+    }
+
+    public WorkflowEntry createEntry(String workflowName) throws StoreException {
+        try {
+            Long id = gd.getNextSeqId("OSWorkflowEntry");
+            GenericValue gv = gd.create("OSWorkflowEntry", UtilMisc.toMap("id", id, "name", workflowName, "initialized", new Integer(0)));
+            gd.storeAll(UtilMisc.toList(gv));
+
+            return new SimpleWorkflowEntry(id.longValue(), workflowName, false);
+        } catch (GenericEntityException e) {
+            throw new StoreException("Could not create workflow entry", e);
+        }
+    }
+
+    public List findCurrentSteps(long entryId) throws StoreException {
+        try {
+            Collection c = gd.findByAnd("OSCurrentStep", UtilMisc.toMap("entryId", new Long(entryId)));
+            ArrayList list = new ArrayList();
+
+            for (Iterator iterator = c.iterator(); iterator.hasNext();) {
+                GenericValue gv = (GenericValue) iterator.next();
+                long id = gv.getLong("id").longValue();
+                int stepId = gv.getInteger("stepId").intValue();
+                int actionId = gv.getInteger("actionId").intValue();
+                String owner = gv.getString("owner");
+                Timestamp startDate = gv.getTimestamp("startDate");
+                Timestamp dueDate = gv.getTimestamp("dueDate");
+                Timestamp finishDate = gv.getTimestamp("finishDate");
+                String status = gv.getString("status");
+                String caller = gv.getString("caller");
+
+                Collection prevGvs = gd.findByAnd("OSCurrentStepPrev", UtilMisc.toMap("id", new Long(id)));
+                long[] prevIds = new long[prevGvs.size()];
+                int i = 0;
+
+                for (Iterator iterator2 = prevGvs.iterator();
+                        iterator2.hasNext();) {
+                    GenericValue prevGv = (GenericValue) iterator2.next();
+                    prevIds[i] = prevGv.getLong("previousId").longValue();
+                    i++;
+                }
+
+                SimpleStep step = new SimpleStep(id, entryId, stepId, actionId, owner, startDate, dueDate, finishDate, status, prevIds, caller);
+                list.add(step);
+            }
+
+            return list;
+        } catch (GenericEntityException e) {
+            throw new StoreException("Could not find current steps for #" + entryId, e);
+        }
+    }
+
+    public WorkflowEntry findEntry(long entryId) throws StoreException {
+        try {
+            GenericValue gv = gd.findByPrimaryKey("OSWorkflowEntry", UtilMisc.toMap("id", new Long(entryId)));
+            String workflowName = gv.getString("name");
+            boolean init = gv.getInteger("initialized").intValue() != 0;
+
+            return new SimpleWorkflowEntry(entryId, workflowName, init);
+        } catch (GenericEntityException e) {
+            throw new StoreException("Could not find workflow entry #" + entryId, e);
+        }
+    }
+
+    public List findHistorySteps(long entryId) throws StoreException {
+        try {
+            Collection c = gd.findByAnd("OSHistoryStep", UtilMisc.toMap("entryId", new Long(entryId)), UtilMisc.toList("id DESC"));
+            ArrayList list = new ArrayList();
+
+            for (Iterator iterator = c.iterator(); iterator.hasNext();) {
+                GenericValue gv = (GenericValue) iterator.next();
+                long id = gv.getLong("id").longValue();
+                int stepId = gv.getInteger("stepId").intValue();
+                int actionId = gv.getInteger("actionId").intValue();
+                String owner = gv.getString("owner");
+                Timestamp startDate = gv.getTimestamp("startDate");
+                Timestamp dueDate = gv.getTimestamp("dueDate");
+                Timestamp finishDate = gv.getTimestamp("finishDate");
+                String status = gv.getString("status");
+                String caller = gv.getString("caller");
+
+                Collection prevGvs = gd.findByAnd("OSHistoryStepPrev", UtilMisc.toMap("id", new Long(id)));
+                long[] prevIds = new long[prevGvs.size()];
+                int i = 0;
+
+                for (Iterator iterator2 = prevGvs.iterator();
+                        iterator2.hasNext();) {
+                    GenericValue prevGv = (GenericValue) iterator2.next();
+                    prevIds[i] = prevGv.getLong("previousId").longValue();
+                    i++;
+                }
+
+                SimpleStep step = new SimpleStep(id, entryId, stepId, actionId, owner, startDate, dueDate, finishDate, status, prevIds, caller);
+                list.add(step);
+            }
+
+            return list;
+        } catch (GenericEntityException e) {
+            throw new StoreException("Could not find history steps for #" + entryId, e);
+        }
+    }
+
+    public void init(Map props) throws StoreException {
+        delegatorName = (String) props.get("delegator");
+
+        if (delegatorName == null) {
+            delegatorName = "default";
+        }
+
+        try {
+            gd = GenericDelegator.getGenericDelegator(delegatorName);
+        } catch (Exception t) {
+            throw new StoreException("Error getting GenericDelegator", t);
+        }
+    }
+
+    public Step markFinished(Step step, int actionId, Date finishDate, String status, String caller) throws StoreException {
+        try {
+            GenericValue gv = gd.findByPrimaryKey("OSCurrentStep", UtilMisc.toMap("id", new Long(step.getId())));
+            gv.set("actionId", new Integer(actionId));
+            gv.set("finishDate", new Timestamp(finishDate.getTime()));
+            gv.set("status", status);
+            gv.set("caller", caller);
+            gd.store(gv);
+
+            SimpleStep theStep = (SimpleStep) step;
+            theStep.setStatus(status);
+            theStep.setFinishDate(finishDate);
+            theStep.setActionId(actionId);
+            theStep.setCaller(caller);
+
+            return theStep;
+        } catch (GenericEntityException e) {
+            throw new StoreException("Error marking step #" + step.getId() + " finished", e);
+        }
+    }
+
+    public void moveToHistory(Step step) throws StoreException {
+        try {
+            Long id = new Long(step.getId());
+            gd.removeByAnd("OSCurrentStep", UtilMisc.toMap("id", id));
+
+            HashMap valueMap = new HashMap();
+            valueMap.put("id", id);
+            valueMap.put("entryId", new Long(step.getEntryId()));
+            valueMap.put("actionId", new Integer(step.getActionId()));
+            valueMap.put("stepId", new Integer(step.getStepId()));
+            valueMap.put("owner", step.getOwner());
+            valueMap.put("startDate", new Timestamp(step.getStartDate().getTime()));
+
+            Timestamp realDueDate = null;
+
+            if (step.getDueDate() != null) {
+                realDueDate = new Timestamp(step.getDueDate().getTime());
+            }
+
+            valueMap.put("dueDate", realDueDate);
+            valueMap.put("finishDate", new Timestamp(step.getFinishDate().getTime()));
+            valueMap.put("status", step.getStatus());
+            valueMap.put("caller", step.getCaller());
+
+            GenericValue gv = gd.create("OSHistoryStep", valueMap);
+            ArrayList storeList = new ArrayList();
+            storeList.add(gv);
+
+            long[] previousIds = step.getPreviousStepIds();
+
+            if (previousIds != null) {
+                for (int i = 0; i < previousIds.length; i++) {
+                    long previousId = previousIds[i];
+                    GenericValue prevGv = gd.create("OSHistoryStepPrev", UtilMisc.toMap("id", id, "previousId", new Long(previousId)));
+                    storeList.add(prevGv);
+                }
+            }
+
+            gd.storeAll(storeList);
+        } catch (GenericEntityException e) {
+            throw new StoreException("Could not move to history step for #" + step.getEntryId(), e);
+        }
+    }
+
+    public List query(WorkflowQuery query) throws StoreException {
+        // not implemented
+        throw new StoreException("queries not implemented for this store");
+    }
+}

src/java/com/opensymphony/workflow/timer/LocalWorkflowJob.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.workflow.timer;
+
+import com.opensymphony.workflow.StoreException;
+import com.opensymphony.workflow.Workflow;
+import com.opensymphony.workflow.WorkflowException;
+import com.opensymphony.workflow.basic.BasicWorkflow;
+
+import org.quartz.*;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision: 1.1.1.1 $
+ */
+public class LocalWorkflowJob implements Job {
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
+        JobDataMap data = jobExecutionContext.getJobDetail().getJobDataMap();
+        long id = data.getLong("entryId");
+        int triggerId = data.getInt("triggerId");
+        String username = data.getString("username");
+        Workflow wf = new BasicWorkflow(username);
+
+        try {
+            wf.executeTriggerFunction(id, triggerId);
+        } catch (WorkflowException e) {
+            throw new JobExecutionException("Error Executing local job", (e.getRootCause() != null) ? e.getRootCause() : e, true);
+        }
+    }
+}

src/java/com/opensymphony/workflow/timer/WorkflowJob.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+/*
+ * Created by IntelliJ IDEA.
+ * User: plightbo
+ * Date: May 22, 2002
+ * Time: 4:10:43 PM
+ */
+package com.opensymphony.workflow.timer;
+
+import com.opensymphony.workflow.StoreException;
+import com.opensymphony.workflow.Workflow;
+import com.opensymphony.workflow.WorkflowException;