Break class cycles between Workflow and Step in order to prepare for JSPWIKI-304 (Workflows are not Serializable)
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/tasks/auth/SaveUserProfileTask.java b/jspwiki-main/src/main/java/org/apache/wiki/tasks/auth/SaveUserProfileTask.java
index 3cb09a0..18325b3 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/tasks/auth/SaveUserProfileTask.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/tasks/auth/SaveUserProfileTask.java
@@ -48,7 +48,7 @@
     @Override
     public Outcome execute() throws WikiException {
         // Retrieve user profile
-        final UserProfile profile = ( UserProfile )getWorkflow().getAttribute( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE );
+        final UserProfile profile = ( UserProfile )getWorkflowContext().get( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE );
 
         // Save the profile (userdatabase will take care of timestamps for us)
         m_engine.getManager( UserManager.class ).getUserDatabase().save( profile );
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/tasks/pages/PreSaveWikiPageTask.java b/jspwiki-main/src/main/java/org/apache/wiki/tasks/pages/PreSaveWikiPageTask.java
index c2ed0ac..5e65649 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/tasks/pages/PreSaveWikiPageTask.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/tasks/pages/PreSaveWikiPageTask.java
@@ -19,14 +19,12 @@
 package org.apache.wiki.tasks.pages;
 
 import org.apache.wiki.api.core.Context;
-import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.api.core.Page;
 import org.apache.wiki.api.exceptions.WikiException;
 import org.apache.wiki.filters.FilterManager;
 import org.apache.wiki.tasks.TasksManager;
 import org.apache.wiki.workflow.Outcome;
 import org.apache.wiki.workflow.Task;
-import org.apache.wiki.workflow.Workflow;
 import org.apache.wiki.workflow.WorkflowManager;
 
 import java.security.Principal;
@@ -60,10 +58,6 @@
      */
     @Override
     public Outcome execute() throws WikiException {
-        // Retrieve attributes
-        final Engine engine = m_context.getEngine();
-        final Workflow workflow = getWorkflow();
-
         // Get the wiki page
         final Page page = m_context.getPage();
 
@@ -76,11 +70,11 @@
         }
 
         // Run the pre-save filters. If any exceptions, add error to list, abort, and redirect
-        final String saveText = engine.getManager( FilterManager.class ).doPreSaveFiltering(m_context, m_proposedText);
+        final String saveText = m_context.getEngine().getManager( FilterManager.class ).doPreSaveFiltering(m_context, m_proposedText);
 
         // Stash the wiki context, old and new text as workflow attributes
-        workflow.setAttribute( WorkflowManager.WF_WP_SAVE_ATTR_PRESAVE_WIKI_CONTEXT, m_context );
-        workflow.setAttribute( WorkflowManager.WF_WP_SAVE_FACT_PROPOSED_TEXT, saveText );
+        getWorkflowContext().put( WorkflowManager.WF_WP_SAVE_ATTR_PRESAVE_WIKI_CONTEXT, m_context );
+        getWorkflowContext().put( WorkflowManager.WF_WP_SAVE_FACT_PROPOSED_TEXT, saveText );
         return Outcome.STEP_COMPLETE;
     }
 
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/tasks/pages/SaveWikiPageTask.java b/jspwiki-main/src/main/java/org/apache/wiki/tasks/pages/SaveWikiPageTask.java
index 3f83d08..0063c40 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/tasks/pages/SaveWikiPageTask.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/tasks/pages/SaveWikiPageTask.java
@@ -51,8 +51,8 @@
     @Override
     public Outcome execute() throws WikiException {
         // Retrieve attributes
-        final Context context = ( Context ) getWorkflow().getAttribute( WorkflowManager.WF_WP_SAVE_ATTR_PRESAVE_WIKI_CONTEXT );
-        final String proposedText = (String) getWorkflow().getAttribute( WorkflowManager.WF_WP_SAVE_FACT_PROPOSED_TEXT );
+        final Context context = ( Context )getWorkflowContext().get( WorkflowManager.WF_WP_SAVE_ATTR_PRESAVE_WIKI_CONTEXT );
+        final String proposedText = ( String )getWorkflowContext().get( WorkflowManager.WF_WP_SAVE_FACT_PROPOSED_TEXT );
 
         final Engine engine = context.getEngine();
         final Page page = context.getPage();
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/AbstractStep.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/AbstractStep.java
index e545990..414aa20 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/AbstractStep.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/AbstractStep.java
@@ -20,7 +20,6 @@
 
 import org.apache.wiki.api.exceptions.WikiException;
 
-import java.io.Serializable;
 import java.security.Principal;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -53,7 +52,10 @@
 
     private final Map< Outcome, Step > m_successors;
 
-    private Workflow m_workflow;
+    private int workflowId;
+
+    /** attribute map. */
+    private Map< String, Object > workflowContext;
 
     private Outcome m_outcome;
 
@@ -63,7 +65,7 @@
 
     /**
      * Protected constructor that creates a new Step with a specified message key. After construction, the method
-     * {@link #setWorkflow(Workflow)} should be called.
+     * {@link #setWorkflow(int, Map)} should be called.
      *
      * @param messageKey the Step's message key, such as {@code decision.editPageApproval}. By convention, the message prefix should
      *                   be a lower-case version of the Step's type, plus a period (<em>e.g.</em>, {@code task.} and {@code decision.}).
@@ -82,13 +84,14 @@
     /**
      * Constructs a new Step belonging to a specified Workflow and having a specified message key.
      *
-     * @param workflow the workflow the Step belongs to
+     * @param workflowId the parent workflow id to set
+     * @param workflowContext the parent workflow context to set
      * @param messageKey the Step's message key, such as {@code decision.editPageApproval}. By convention, the message prefix should
      *                   be a lower-case version of the Step's type, plus a period (<em>e.g.</em>, {@code task.} and {@code decision.}).
      */
-    public AbstractStep( final Workflow workflow, final String messageKey ) {
+    public AbstractStep( final int workflowId, final Map< String, Object > workflowContext, final String messageKey ) {
         this( messageKey );
-        setWorkflow( workflow );
+        setWorkflow( workflowId, workflowContext );
     }
 
     /**
@@ -133,16 +136,6 @@
     /**
      * {@inheritDoc}
      */
-    public final Serializable[] getMessageArguments() {
-        if( m_workflow == null ) {
-            return new Serializable[ 0 ];
-        }
-        return m_workflow.getMessageArguments();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
     public final String getMessageKey() {
         return m_key;
     }
@@ -157,16 +150,6 @@
     /**
      * {@inheritDoc}
      */
-    public Principal getOwner() {
-        if( m_workflow == null ) {
-            return null;
-        }
-        return m_workflow.getOwner();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
     public final Date getStartTime() {
         return m_start;
     }
@@ -174,13 +157,6 @@
     /**
      * {@inheritDoc}
      */
-    public final synchronized Workflow getWorkflow() {
-        return m_workflow;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
     public final boolean isCompleted() {
         return m_completed;
     }
@@ -235,12 +211,22 @@
     // --------------------------Helper methods--------------------------
 
     /**
-     * method that sets the parent Workflow post-construction.
+     * method that sets the parent Workflow id and context post-construction.
      *
-     * @param workflow the parent workflow to set
+     * @param workflowId the parent workflow id to set
+     * @param workflowContext the parent workflow context to set
      */
-    public final synchronized void setWorkflow( final Workflow workflow ) {
-        m_workflow = workflow;
+    public final synchronized void setWorkflow( final int workflowId, final Map< String, Object > workflowContext ) {
+        this.workflowId = workflowId;
+        this.workflowContext = workflowContext;
+    }
+
+    public int getWorkflowId() {
+        return workflowId;
+    }
+
+    public Map< String, Object > getWorkflowContext() {
+        return workflowContext;
     }
 
     /**
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/Decision.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/Decision.java
index dbbabed..07749c1 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/Decision.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/Decision.java
@@ -25,6 +25,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 
 /**
@@ -61,13 +62,14 @@
     /**
      * Constructs a new Decision for a required "actor" Principal, having a default Outcome.
      *
-     * @param workflow the parent Workflow object
+     * @param workflowId the parent workflow id to set
+     * @param workflowContext the parent workflow context to set
      * @param messageKey the i18n message key that represents the message the actor will see
      * @param actor the Principal (<em>e.g.</em>, a WikiPrincipal, Role, GroupPrincipal) who is required to select an appropriate Outcome
      * @param defaultOutcome the Outcome that the user interface will recommend as the default choice
      */
-    public Decision( final Workflow workflow, final String messageKey, final Principal actor, final Outcome defaultOutcome ) {
-        super( workflow, messageKey );
+    public Decision( final int workflowId, final Map< String, Object > workflowContext, final String messageKey, final Principal actor, final Outcome defaultOutcome ) {
+        super( workflowId, workflowContext, messageKey );
         m_actor = actor;
         m_defaultOutcome = defaultOutcome;
         m_facts = new ArrayList<>();
@@ -104,14 +106,7 @@
      */
     public void decide( final Outcome outcome ) throws WikiException {
         super.setOutcome( outcome );
-
-        // If current workflow is waiting for input, restart it and remove Decision from DecisionQueue
-        final Workflow w = getWorkflow();
-        if( w.getCurrentState() == Workflow.WAITING && this.equals( w.getCurrentStep() ) ) {
-            WorkflowEventEmitter.fireEvent( this, WorkflowEvent.DQ_REMOVAL );
-            // Restart workflow
-            w.restart();
-        }
+        WorkflowEventEmitter.fireEvent( this, WorkflowEvent.DQ_REMOVAL );
     }
 
     /**
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/DefaultWorkflowManager.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/DefaultWorkflowManager.java
index d837611..723b5c0 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/DefaultWorkflowManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/DefaultWorkflowManager.java
@@ -18,19 +18,20 @@
  */
 package org.apache.wiki.workflow;
 
+import org.apache.log4j.Logger;
 import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.api.core.Session;
 import org.apache.wiki.api.exceptions.WikiException;
 import org.apache.wiki.auth.AuthorizationManager;
 import org.apache.wiki.auth.acl.UnresolvedPrincipal;
 import org.apache.wiki.event.WikiEvent;
-import org.apache.wiki.event.WikiEventManager;
 import org.apache.wiki.event.WorkflowEvent;
 
 import java.security.Principal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -45,6 +46,8 @@
  */
 public class DefaultWorkflowManager implements WorkflowManager {
 
+    private static final Logger LOG = Logger.getLogger( DefaultWorkflowManager.class );
+
     private final DecisionQueue m_queue = new DecisionQueue();
     private final Set< Workflow > m_workflows;
     private final Map< String, Principal > m_approvers;
@@ -52,15 +55,13 @@
     private Engine m_engine = null;
 
     /**
-     * Constructs a new WorkflowManager, with an empty workflow cache. New Workflows are automatically assigned unique identifiers,
-     * starting with 1.
+     * Constructs a new WorkflowManager, with an empty workflow cache.
      */
     public DefaultWorkflowManager() {
-        m_next = 1;
         m_workflows = ConcurrentHashMap.newKeySet();
         m_approvers = new ConcurrentHashMap<>();
         m_completed = new CopyOnWriteArrayList<>();
-        WikiEventManager.addWikiEventListener( WorkflowEventEmitter.get(), this );
+        WorkflowEventEmitter.registerListener( this );
     }
 
     /**
@@ -68,8 +69,6 @@
      */
     @Override
     public void start( final Workflow workflow ) throws WikiException {
-        m_workflows.add( workflow );
-        workflow.setId( nextId() );
         workflow.start();
     }
 
@@ -176,19 +175,6 @@
         return m_queue;
     }
 
-    private volatile int m_next;
-
-    /**
-     * Returns the next available unique identifier, which is subsequently incremented.
-     *
-     * @return the id
-     */
-    private synchronized int nextId() {
-        final int current = m_next;
-        m_next++;
-        return current;
-    }
-
     /**
      * {@inheritDoc}
      */
@@ -251,9 +237,6 @@
      * @param workflow the workflow to add
      */
     protected void add( final Workflow workflow ) {
-        if ( workflow.getId() == Workflow.ID_NOT_SET ) {
-            workflow.setId( nextId() );
-        }
         m_workflows.add( workflow );
     }
 
@@ -271,7 +254,21 @@
     }
 
     protected void removeFromDecisionQueue( final Decision decision ) {
-        getDecisionQueue().remove( decision );
+        // If current workflow is waiting for input, restart it and remove Decision from DecisionQueue
+        final int workflowId = decision.getWorkflowId();
+        final Optional< Workflow > optw = m_workflows.stream().filter( w -> w.getId() == workflowId ).findAny();
+        if( optw.isPresent() ) {
+            final Workflow w = optw.get();
+            if( w.getCurrentState() == Workflow.WAITING && decision.equals( w.getCurrentStep() ) ) {
+                getDecisionQueue().remove( decision );
+                // Restart workflow
+                try {
+                    w.restart();
+                } catch( final WikiException e ) {
+                    LOG.error( "restarting workflow #" + w.getId() + " caused " + e.getMessage(), e );
+                }
+            }
+        }
     }
 
     protected void addToDecisionQueue( final Decision decision ) {
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/SimpleDecision.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/SimpleDecision.java
index 181c355..bae296a 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/SimpleDecision.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/SimpleDecision.java
@@ -19,30 +19,27 @@
 package org.apache.wiki.workflow;
 
 import java.security.Principal;
+import java.util.Map;
+
 
 /**
- * Decision subclass that includes two available Outcomes:
- * {@link Outcome#DECISION_APPROVE} or {@link Outcome#DECISION_DENY}.
- * The Decision is reassignable, and the default Outcome is 
- * {@link Outcome#DECISION_APPROVE}.
- * 
+ * Decision subclass that includes two available Outcomes: {@link Outcome#DECISION_APPROVE} or {@link Outcome#DECISION_DENY}.
+ * The Decision is reassignable, and the default Outcome is {@link Outcome#DECISION_APPROVE}.
  */
-public class SimpleDecision extends Decision
-{
+public class SimpleDecision extends Decision {
 
     private static final long serialVersionUID = 8192213077644617341L;
 
     /**
      * Constructs a new SimpleDecision assigned to a specified actor.
-     * @param workflow the parent Workflow
-     * @param messageKey the message key that describes the Decision, which
-     * will be presented in the UI
-     * @param actor the Principal (<em>e.g.</em>, WikiPrincipal,
-     * GroupPrincipal, Role) who will decide
+     *
+     * @param workflowId the parent workflow id to set
+     * @param workflowContext the parent workflow context to set
+     * @param messageKey the message key that describes the Decision, which will be presented in the UI
+     * @param actor the Principal (<em>e.g.</em>, WikiPrincipal, GroupPrincipal, Role) who will decide
      */
-    public SimpleDecision( Workflow workflow, String messageKey, Principal actor )
-    {
-        super( workflow, messageKey, actor, Outcome.DECISION_APPROVE );
+    public SimpleDecision( final int workflowId, final Map< String, Object > workflowContext, final String messageKey, final Principal actor ) {
+        super( workflowId, workflowContext, messageKey, actor, Outcome.DECISION_APPROVE );
 
         // Add the other default outcomes
         super.addSuccessor( Outcome.DECISION_DENY, null );
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/SimpleNotification.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/SimpleNotification.java
index d2cfdb0..44d88a2 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/SimpleNotification.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/SimpleNotification.java
@@ -18,59 +18,50 @@
  */
 package org.apache.wiki.workflow;
 
-import java.security.Principal;
-
 import org.apache.wiki.api.exceptions.WikiException;
 
+import java.security.Principal;
+import java.util.Map;
+
 /**
- * Decision subclass used for notifications that includes only one available Outcome:
- * {@link Outcome#DECISION_ACKNOWLEDGE}. The Decision is not reassignable, and
- * the default Outcome is {@link Outcome#DECISION_ACKNOWLEDGE}.
+ * Decision subclass used for notifications that includes only one available Outcome: {@link Outcome#DECISION_ACKNOWLEDGE}. The Decision is
+ * not reassignable, and the default Outcome is {@link Outcome#DECISION_ACKNOWLEDGE}.
  * 
  * @since 2.5
  */
-public final class SimpleNotification extends Decision
-{
+public final class SimpleNotification extends Decision {
 
     private static final long serialVersionUID = -3392947495169819527L;
 
     /**
-     * Constructs a new SimpleNotification object with a supplied message key,
-     * associated Workflow, and named actor who must acknowledge the message.
-     * The notification is placed in the Principal's list of queued Decisions.
-     * Because the only available Outcome is
-     * {@link Outcome#DECISION_ACKNOWLEDGE}, the actor can only acknowledge the
-     * message.
+     * Constructs a new SimpleNotification object with a supplied message key, associated Workflow, and named actor who must acknowledge
+     * the message. The notification is placed in the Principal's list of queued Decisions. Because the only available Outcome is
+     * {@link Outcome#DECISION_ACKNOWLEDGE}, the actor can only acknowledge the message.
      * 
-     * @param workflow
-     *            the Workflow to associate this notification with
-     * @param messageKey
-     *            the message key
-     * @param actor
-     *            the Principal who will acknowledge the message
+     * @param workflowId the parent workflow id to set
+     * @param workflowContext the parent workflow context to set
+     * @param messageKey the message key
+     * @param actor the Principal who will acknowledge the message
      */
-    public SimpleNotification( Workflow workflow, String messageKey, Principal actor )
-    {
-        super( workflow, messageKey, actor, Outcome.DECISION_ACKNOWLEDGE );
+    public SimpleNotification( final int workflowId, final Map< String, Object > workflowContext, final String messageKey, final Principal actor ) {
+        super( workflowId, workflowContext, messageKey, actor, Outcome.DECISION_ACKNOWLEDGE );
     }
     
     /**
-     * Convenience method that simply calls {@link #decide(Outcome)} 
-     * with the value {@link Outcome#DECISION_ACKNOWLEDGE}.
+     * Convenience method that simply calls {@link #decide(Outcome)} with the value {@link Outcome#DECISION_ACKNOWLEDGE}.
+     *
      * @throws WikiException never
      */
-    public void acknowledge() throws WikiException
-    {
+    public void acknowledge() throws WikiException {
         this.decide( Outcome.DECISION_ACKNOWLEDGE  );
     }
 
     /**
-     * Notifications cannot be re-assigned, so this method always returns
-     * <code>false</code>.
-     * @return <code>false</code> always
+     * Notifications cannot be re-assigned, so this method always returns <code>false</code>.
+     *
+     * @return <code>false</code> always.
      */
-    public boolean isReassignable()
-    {
+    public boolean isReassignable() {
         return false;
     }
 
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/Step.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/Step.java
index 50e3327..5d890cc 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/Step.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/Step.java
@@ -25,6 +25,8 @@
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
+
 
 /**
  * <p>
@@ -124,14 +126,6 @@
     String getMessageKey();
 
     /**
-     * Returns the message arguments for this Step, typically by delegating to the parent Workflow's {@link Workflow#getMessageArguments()}
-     * method.
-     * 
-     * @return the message arguments.
-     */
-    Serializable[] getMessageArguments();
-
-    /**
      * Returns the Outcome of this Step's processing; by default, {@link Outcome#STEP_CONTINUE}.
      * 
      * @return the outcome
@@ -181,13 +175,6 @@
     void setOutcome( Outcome outcome );
 
     /**
-     * Convenience method that returns the owner of the Workflow by delegating to {@link Workflow#getOwner()}.
-     * 
-     * @return the owner of the Workflow
-     */
-    Principal getOwner();
-
-    /**
      * Identifies the next Step for a particular Outcome; if there is no next Step for this Outcome, this method returns <code>null</code>.
      * 
      * @param outcome the outcome
@@ -196,17 +183,11 @@
     Step getSuccessor( Outcome outcome );
 
     /**
-     * Gets the Workflow that is the parent of this Step.
-     *
-     * @return the workflow
-     */
-    Workflow getWorkflow();
-
-    /**
      * Sets the parent Workflow post-construction. Should be called after building a {@link Step}.
      *
-     * @param workflow the parent workflow to set
+     * @param workflowId the parent workflow id to set
+     * @param workflowContext the parent workflow context to set
      */
-    void setWorkflow( Workflow workflow );
+    void setWorkflow( final int workflowId, final Map< String, Object > workflowContext );
 
 }
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/Task.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/Task.java
index dd88086..5e560ee 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/Task.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/Task.java
@@ -19,93 +19,77 @@
 package org.apache.wiki.workflow;
 
 import java.security.Principal;
+import java.util.Map;
+
 
 /**
- * AbstractStep subclass that executes instructions, uninterrupted, and results
- * in an Outcome. Concrete classes only need to implement {@link Task#execute()}.
- * When the execution step completes, <code>execute</code> must return
- * {@link Outcome#STEP_COMPLETE}, {@link Outcome#STEP_CONTINUE} or
- * {@link Outcome#STEP_ABORT}. Subclasses can add any errors by calling the
- * helper method {@link AbstractStep#addError(String)}. The execute method should
- * <em>generally</em> capture and add errors to the error list instead of
+ * AbstractStep subclass that executes instructions, uninterrupted, and results in an Outcome. Concrete classes only need to implement
+ * {@link Task#execute()}. When the execution step completes, <code>execute</code> must return {@link Outcome#STEP_COMPLETE},
+ * {@link Outcome#STEP_CONTINUE} or {@link Outcome#STEP_ABORT}. Subclasses can add any errors by calling the helper method
+ * {@link AbstractStep#addError(String)}. The execute method should <em>generally</em> capture and add errors to the error list instead of
  * throwing a WikiException.
  * <p>
  *
  * @since 2.5
  */
-public abstract class Task extends AbstractStep
-{
+public abstract class Task extends AbstractStep {
+
     private static final long serialVersionUID = 4630293957752430807L;
-    
+
     private Step m_successor = null;
 
     /**
-     * Protected constructor that creates a new Task with a specified message key.
-     * After construction, the protected method {@link #setWorkflow(Workflow)} should be
-     * called.
+     * Public constructor that creates a new Task with a specified message key. After construction, the protected method
+     * {@link #setWorkflow(int, Map)} should be called.
      *
-     * @param messageKey
-     *            the Step's message key, such as
-     *            <code>decision.editPageApproval</code>. By convention, the
-     *            message prefix should be a lower-case version of the Step's
-     *            type, plus a period (<em>e.g.</em>, <code>task.</code>
-     *            and <code>decision.</code>).
+     * @param messageKey the Step's message key, such as <code>decision.editPageApproval</code>. By convention, the message prefix should be
+     *                   a lower-case version of the Step's type, plus a period (<em>e.g.</em>, <code>task.</code> and <code>decision.</code>).
      */
-    public Task( String messageKey )
-    {
+    public Task( final String messageKey ) {
         super( messageKey );
         super.addSuccessor( Outcome.STEP_COMPLETE, null );
         super.addSuccessor( Outcome.STEP_ABORT, null );
     }
 
     /**
-     * Constructs a new instance of a Task, with an associated Workflow and
-     * message key.
+     * Constructs a new instance of a Task, with an associated Workflow and message key.
      *
-     * @param workflow
-     *            the associated workflow
-     * @param messageKey
-     *            the i18n message key
+     * @param workflowId      the parent workflow id to set
+     * @param workflowContext the parent workflow context to set
+     * @param messageKey      the i18n message key
      */
-    public Task( Workflow workflow, String messageKey )
-    {
+    public Task( final int workflowId, final Map< String, Object > workflowContext, final String messageKey ) {
         this( messageKey );
-        setWorkflow( workflow );
+        setWorkflow( workflowId, workflowContext );
     }
 
     /**
      * Returns {@link SystemPrincipal#SYSTEM_USER}.
+     *
      * @return the system principal
      */
-    public final Principal getActor()
-    {
+    public final Principal getActor() {
         return SystemPrincipal.SYSTEM_USER;
     }
 
     /**
-     * Sets the successor Step to this one, which will be triggered if the Task
-     * completes successfully (that is, {@link Step#getOutcome()} returns
-     * {@link Outcome#STEP_COMPLETE}. This method is really a convenient
-     * shortcut for {@link Step#addSuccessor(Outcome, Step)}, where the first
-     * parameter is {@link Outcome#STEP_COMPLETE}.
+     * Sets the successor Step to this one, which will be triggered if the Task completes successfully (that is, {@link Step#getOutcome()}
+     * returns {@link Outcome#STEP_COMPLETE}. This method is really a convenient shortcut for {@link Step#addSuccessor(Outcome, Step)},
+     * where the first parameter is {@link Outcome#STEP_COMPLETE}.
      *
-     * @param step
-     *            the successor
+     * @param step the successor
      */
-    public final synchronized void setSuccessor( Step step )
-    {
+    public final synchronized void setSuccessor( final Step step ) {
         m_successor = step;
     }
 
     /**
-     * Identifies the next Step after this Task finishes successfully. This
-     * method will always return the value set in method
+     * Identifies the next Step after this Task finishes successfully. This method will always return the value set in method
      * {@link #setSuccessor(Step)}, regardless of the current completion state.
      *
      * @return the next step
      */
-    public final synchronized Step getSuccessor()
-    {
+    public final synchronized Step getSuccessor() {
         return m_successor;
     }
 
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/Workflow.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/Workflow.java
index 7657b27..42c6c2f 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/Workflow.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/Workflow.java
@@ -30,6 +30,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
 
 /**
@@ -164,6 +165,8 @@
 
     private static final long serialVersionUID = 5228149040690660032L;
 
+    private static final AtomicInteger idsCounter = new AtomicInteger( 1 );
+
     /** ID value: the workflow ID has not been set. */
     public static final int ID_NOT_SET = 0;
 
@@ -217,12 +220,13 @@
         m_attributes = new ConcurrentHashMap<>();
         m_currentStep = null;
         m_history = new LinkedList<>();
-        m_id = ID_NOT_SET;
+        m_id = idsCounter.getAndIncrement();
         m_key = messageKey;
         m_messageArgs = new ArrayList<>();
         m_owner = owner;
         m_started = false;
         m_state = CREATED;
+        WorkflowEventEmitter.fireEvent( this, WorkflowEvent.CREATED );
     }
 
     /**
@@ -243,7 +247,7 @@
 
         if( m_currentStep != null ) {
             if( m_currentStep instanceof Decision ) {
-                WorkflowEventEmitter.fireEvent( ( Decision )m_currentStep, WorkflowEvent.DQ_REMOVAL );
+                WorkflowEventEmitter.fireEvent( m_currentStep, WorkflowEvent.DQ_REMOVAL );
             }
             m_currentStep.setOutcome( Outcome.STEP_ABORT );
             m_history.addLast( m_currentStep );
@@ -313,6 +317,15 @@
     }
 
     /**
+     * Retrieves workflow's attributes.
+     *
+     * @return workflow's attributes.
+     */
+    public final Map< String, Object > getAttributes() {
+        return m_attributes;
+    }
+
+    /**
      * The end time for this Workflow, expressed as a system time number. This value is equal to the end-time value returned by the final
      * Step's {@link Step#getEndTime()} method, if the workflow has completed. Otherwise, this method returns {@link Step#TIME_NOT_SET}.
      *
@@ -461,6 +474,7 @@
         if( m_state != WAITING ) {
             throw new IllegalStateException( "Workflow is not paused; cannot restart." );
         }
+        WorkflowEventEmitter.fireEvent( this, WorkflowEvent.STARTED );
         m_state = RUNNING;
         WorkflowEventEmitter.fireEvent( this, WorkflowEvent.RUNNING );
 
@@ -522,10 +536,11 @@
         if( m_started ) {
             throw new IllegalStateException( "Workflow has already started." );
         }
+        WorkflowEventEmitter.fireEvent( this, WorkflowEvent.STARTED );
         m_started = true;
         m_state = RUNNING;
-        WorkflowEventEmitter.fireEvent( this, WorkflowEvent.RUNNING );
 
+        WorkflowEventEmitter.fireEvent( this, WorkflowEvent.RUNNING );
         // Mark the first step as the current one & add to history
         m_currentStep = m_firstStep;
         m_history.add( m_currentStep );
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowBuilder.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowBuilder.java
index 9a8e7f2..b497147 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowBuilder.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowBuilder.java
@@ -117,7 +117,7 @@
         if ( decisionRequired ) {
             // Look up the name of the approver (user or group) listed in jspwiki.properties; approvals go to the approver's decision queue
             final Principal approverPrincipal = mgr.getApprover( workflowApproverKey );
-            final Decision decision = new SimpleDecision( workflow, decisionKey, approverPrincipal );
+            final Decision decision = new SimpleDecision( workflow.getId(), workflow.getAttributes(), decisionKey, approverPrincipal );
 
             // Add facts to the Decision, if any were supplied
             if( facts != null ) {
@@ -132,7 +132,7 @@
 
             // If rejected, sent a notification
             if ( rejectedMessageKey != null ) {
-                final SimpleNotification rejectNotification = new SimpleNotification( workflow, rejectedMessageKey, submitter );
+                final SimpleNotification rejectNotification = new SimpleNotification( workflow.getId(), workflow.getAttributes(), rejectedMessageKey, submitter );
                 decision.addSuccessor( Outcome.DECISION_DENY, rejectNotification );
             }
 
@@ -158,9 +158,9 @@
 
         // Make sure our tasks have this workflow as the parent, then return
         if( prepTask != null ) {
-            prepTask.setWorkflow( workflow );
+            prepTask.setWorkflow( workflow.getId(), workflow.getAttributes() );
         }
-        completionTask.setWorkflow( workflow );
+        completionTask.setWorkflow( workflow.getId(), workflow.getAttributes() );
         return workflow;
     }
 
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowEventEmitter.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowEventEmitter.java
index 8c2f899..8ecabd5 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowEventEmitter.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowEventEmitter.java
@@ -18,9 +18,12 @@
  */
 package org.apache.wiki.workflow;
 
+import org.apache.wiki.event.WikiEventListener;
 import org.apache.wiki.event.WikiEventManager;
 import org.apache.wiki.event.WorkflowEvent;
 
+import java.util.Set;
+
 
 /**
  * Emits all kind of {@link WorkflowEvent}s.
@@ -39,4 +42,14 @@
         }
     }
 
+    public static void registerListener( final WikiEventListener listener ) {
+        if ( WikiEventManager.isListening( get() ) ) {
+            final Set< WikiEventListener > attachedListeners = WikiEventManager.getWikiEventListeners( get() );
+            attachedListeners.stream()
+                             .filter( l -> listener.getClass().isAssignableFrom( l.getClass() ) )
+                             .forEach( WikiEventManager::removeWikiEventListener );
+        }
+        WikiEventManager.addWikiEventListener( WorkflowEventEmitter.get(), listener );
+    }
+
 }
diff --git a/jspwiki-main/src/test/java/org/apache/wiki/auth/UserManagerTest.java b/jspwiki-main/src/test/java/org/apache/wiki/auth/UserManagerTest.java
index 7565109..aecf7de 100644
--- a/jspwiki-main/src/test/java/org/apache/wiki/auth/UserManagerTest.java
+++ b/jspwiki-main/src/test/java/org/apache/wiki/auth/UserManagerTest.java
@@ -326,7 +326,7 @@
         Assertions.assertEquals( new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_LOGIN_NAME, profile.getLoginName() ), facts.get( 1 ) );
         Assertions.assertEquals( new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_SUBMITTER, session.getUserPrincipal().getName() ), facts.get( 2 ) );
         Assertions.assertEquals( new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_EMAIL, profile.getEmail() ), facts.get( 3 ) );
-        Assertions.assertEquals( profile, d.getWorkflow().getAttribute( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE ) );
+        Assertions.assertEquals( profile, d.getWorkflowContext().get( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE ) );
 
         // Approve the profile
         d.decide( Outcome.DECISION_APPROVE );
@@ -377,7 +377,7 @@
         Assertions.assertEquals( new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_LOGIN_NAME, profile.getLoginName() ), facts.get( 1 ) );
         Assertions.assertEquals( new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_SUBMITTER, session.getUserPrincipal().getName() ), facts.get( 2 ) );
         Assertions.assertEquals( new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_EMAIL, profile.getEmail() ), facts.get( 3 ) );
-        Assertions.assertEquals( profile, d.getWorkflow().getAttribute( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE ) );
+        Assertions.assertEquals( profile, d.getWorkflowContext().get( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE ) );
 
         // Approve the profile
         d.decide( Outcome.DECISION_DENY );
diff --git a/jspwiki-main/src/test/java/org/apache/wiki/workflow/ApprovalWorkflowTest.java b/jspwiki-main/src/test/java/org/apache/wiki/workflow/ApprovalWorkflowTest.java
index 7f25b9e..ba05915 100644
--- a/jspwiki-main/src/test/java/org/apache/wiki/workflow/ApprovalWorkflowTest.java
+++ b/jspwiki-main/src/test/java/org/apache/wiki/workflow/ApprovalWorkflowTest.java
@@ -28,36 +28,22 @@
 import org.apache.wiki.filters.FilterManager;
 import org.apache.wiki.pages.PageManager;
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import java.security.Principal;
 import java.util.Collection;
 import java.util.List;
-import java.util.Properties;
+
+import static org.apache.wiki.TestEngine.with;
+
 
 public class ApprovalWorkflowTest {
 
-    WorkflowBuilder m_builder;
-    TestEngine m_engine;
-    WorkflowManager m_wm;
-    DecisionQueue m_dq;
-
-    @BeforeEach
-    public void setUp() throws Exception {
-        final Properties props = TestEngine.getTestProperties();
-
-        // Explicitly turn on Admin approvals for page saves and our sample approval workflow
-        props.put("jspwiki.approver.workflow.saveWikiPage", "Admin");
-        props.put( "jspwiki.approver.workflow.approvalWorkflow", Users.JANNE );
-
-        // Start the wiki engine
-        m_engine = new TestEngine( props );
-        m_wm = m_engine.getManager( WorkflowManager.class );
-        m_dq = m_wm.getDecisionQueue();
-        m_builder = WorkflowBuilder.getBuilder( m_engine );
-    }
-
+    static TestEngine m_engine = TestEngine.build( with( "jspwiki.approver.workflow.saveWikiPage", "Admin" ),
+                                                   with( "jspwiki.approver.workflow.approvalWorkflow", Users.JANNE ) );
+    static WorkflowManager m_wm = m_engine.getManager( WorkflowManager.class );
+    static DecisionQueue m_dq = m_wm.getDecisionQueue();
+    static WorkflowBuilder m_builder = WorkflowBuilder.getBuilder( m_engine );
 
     @Test
     public void testBuildApprovalWorkflow() throws WikiException {
@@ -66,15 +52,15 @@
         final Task prepTask = new TestPrepTask( "task.preSaveWikiPage" );
         final String decisionKey = "decision.saveWikiPage";
         final Fact[] facts = new Fact[3];
-        facts[0] = new Fact("fact1", 1 );
-        facts[1] = new Fact("fact2","A factual String");
-        facts[2] = new Fact("fact3",Outcome.DECISION_ACKNOWLEDGE);
+        facts[ 0 ] = new Fact( "fact1", 1 );
+        facts[ 1 ] = new Fact( "fact2", "A factual String" );
+        facts[ 2 ] = new Fact( "fact3", Outcome.DECISION_ACKNOWLEDGE );
         final Task completionTask = new TestPrepTask( "task.saveWikiPage" );
         final String rejectedMessageKey = "notification.saveWikiPage.reject";
 
-        final Workflow w = m_builder.buildApprovalWorkflow(submitter, workflowApproverKey,
-                                                   prepTask, decisionKey, facts,
-                                                   completionTask, rejectedMessageKey);
+        final Workflow w = m_builder.buildApprovalWorkflow( submitter, workflowApproverKey,
+                                                            prepTask, decisionKey, facts,
+                                                            completionTask, rejectedMessageKey );
 
         // Check to see if the workflow built correctly
         Assertions.assertFalse( w.isStarted() || w.isCompleted() || w.isAborted() );
@@ -137,10 +123,10 @@
         final String workflowApproverKey = "workflow.approvalWorkflow";
         final Task prepTask = new TestPrepTask( "task.preSaveWikiPage" );
         final String decisionKey = "decision.saveWikiPage";
-        final Fact[] facts = new Fact[3];
-        facts[0] = new Fact("fact1", 1 );
-        facts[1] = new Fact("fact2","A factual String");
-        facts[2] = new Fact("fact3",Outcome.DECISION_ACKNOWLEDGE);
+        final Fact[] facts = new Fact[ 3 ];
+        facts[ 0 ] = new Fact( "fact1", 1 );
+        facts[ 1 ] = new Fact( "fact2", "A factual String" );
+        facts[ 2 ] = new Fact( "fact3", Outcome.DECISION_ACKNOWLEDGE );
         final Task completionTask = new TestPrepTask( "task.saveWikiPage" );
         final String rejectedMessageKey = "notification.saveWikiPage.reject";
 
@@ -177,25 +163,23 @@
         // Create a sample test page and try to save it
         final String pageName = "SaveWikiPageWorkflow-Test" + System.currentTimeMillis();
         final String text = "This is a test!";
-        try {
-            m_engine.saveTextAsJanne( pageName, text );
-        } catch( final DecisionRequiredException e ) {
-            // Swallow exception, because it is expected...
-        }
+
+        // Expected exception...
+        Assertions.assertThrows( DecisionRequiredException.class, () -> m_engine.saveTextAsJanne( pageName, text ) );
 
         // How do we know the workflow works? Well, first of all the page shouldn't exist yet...
-        Assertions.assertFalse( m_engine.getManager( PageManager.class ).wikiPageExists(pageName));
+        Assertions.assertFalse( m_engine.getManager( PageManager.class ).wikiPageExists( pageName ) );
 
         // Second, GroupPrincipal Admin should see a Decision in its queue
         Collection< Decision > decisions = m_dq.getActorDecisions( m_engine.adminSession() );
-        Assertions.assertEquals(1, decisions.size());
+        Assertions.assertEquals( 1, decisions.size() );
 
         // Now, approve the decision and it should go away, and page should appear.
         final Decision decision = decisions.iterator().next();
-        decision.decide(Outcome.DECISION_APPROVE);
-        Assertions.assertTrue( m_engine.getManager( PageManager.class ).wikiPageExists(pageName));
+        decision.decide( Outcome.DECISION_APPROVE );
+        Assertions.assertTrue( m_engine.getManager( PageManager.class ).wikiPageExists( pageName ) );
         decisions = m_dq.getActorDecisions( m_engine.adminSession() );
-        Assertions.assertEquals(0, decisions.size());
+        Assertions.assertEquals( 0, decisions.size() );
 
         // Delete the page we created
         m_engine.getManager( PageManager.class ).deletePage( pageName );
@@ -213,27 +197,27 @@
         }
 
         // How do we know the workflow works? Well, first of all the page shouldn't exist yet...
-        Assertions.assertFalse( m_engine.getManager( PageManager.class ).wikiPageExists(pageName));
+        Assertions.assertFalse( m_engine.getManager( PageManager.class ).wikiPageExists( pageName ) );
 
         // ...and there should be a Decision in GroupPrincipal Admin's queue
         Collection< Decision > decisions = m_dq.getActorDecisions( m_engine.adminSession() );
-        Assertions.assertEquals(1, decisions.size());
+        Assertions.assertEquals( 1, decisions.size() );
 
         // Now, DENY the decision and the page should still not exist...
         Decision decision = decisions.iterator().next();
-        decision.decide(Outcome.DECISION_DENY);
-        Assertions.assertFalse( m_engine.getManager( PageManager.class ).wikiPageExists(pageName) );
+        decision.decide( Outcome.DECISION_DENY );
+        Assertions.assertFalse( m_engine.getManager( PageManager.class ).wikiPageExists( pageName ) );
 
         // ...but there should also be a notification decision in Janne's queue
         decisions = m_dq.getActorDecisions( m_engine.janneSession() );
-        Assertions.assertEquals(1, decisions.size());
+        Assertions.assertEquals( 1, decisions.size() );
         decision = decisions.iterator().next();
-        Assertions.assertEquals(WorkflowManager.WF_WP_SAVE_REJECT_MESSAGE_KEY, decision.getMessageKey());
+        Assertions.assertEquals( WorkflowManager.WF_WP_SAVE_REJECT_MESSAGE_KEY, decision.getMessageKey() );
 
         // Once Janne disposes of the notification, his queue should be empty
-        decision.decide(Outcome.DECISION_ACKNOWLEDGE);
+        decision.decide( Outcome.DECISION_ACKNOWLEDGE );
         decisions = m_dq.getActorDecisions( m_engine.janneSession() );
-        Assertions.assertEquals(0, decisions.size());
+        Assertions.assertEquals( 0, decisions.size() );
     }
 
     @Test
@@ -245,12 +229,9 @@
         // Create a sample test page and try to save it
         final String pageName = "SaveWikiPageWorkflow-Test" + System.currentTimeMillis();
         final String text = "This is a test!";
-        try
-        {
+        try {
             m_engine.saveTextAsJanne(pageName, text);
-        }
-        catch ( final WikiException e )
-        {
+        } catch( final WikiException e ) {
             Assertions.assertTrue( e instanceof FilterException );
             Assertions.assertEquals( "Page save aborted.", e.getMessage() );
             return;
@@ -262,8 +243,8 @@
      * Sample "prep task" that sets an attribute in the workflow indicating
      * that it ran successfully,
      */
-    public static class TestPrepTask extends Task
-    {
+    public static class TestPrepTask extends Task {
+
         private static final long serialVersionUID = 1L;
 
         public TestPrepTask( final String messageKey )
@@ -273,7 +254,7 @@
 
         @Override
         public Outcome execute() {
-            getWorkflow().setAttribute( getMessageKey(), "Completed" );
+            getWorkflowContext().put( getMessageKey(), "Completed" );
             setOutcome( Outcome.STEP_COMPLETE );
             return Outcome.STEP_COMPLETE;
         }
diff --git a/jspwiki-main/src/test/java/org/apache/wiki/workflow/DecisionQueueTest.java b/jspwiki-main/src/test/java/org/apache/wiki/workflow/DecisionQueueTest.java
index 9ac1201..fdcf1f9 100644
--- a/jspwiki-main/src/test/java/org/apache/wiki/workflow/DecisionQueueTest.java
+++ b/jspwiki-main/src/test/java/org/apache/wiki/workflow/DecisionQueueTest.java
@@ -54,12 +54,12 @@
         adminSession = m_engine.adminSession();
         janneSession = m_engine.janneSession();
         w = new Workflow("workflow.key", new WikiPrincipal("Owner1"));
-        d1 = new SimpleDecision(w, "decision1.key", new GroupPrincipal("Admin"));
-        d2 = new SimpleDecision(w, "decision2.key", new WikiPrincipal("Owner2"));
-        d3 = new SimpleDecision(w, "decision3.key", janneSession.getUserPrincipal());
-        m_queue.add(d1);
-        m_queue.add(d2);
-        m_queue.add(d3);
+        d1 = new SimpleDecision( w.getId(), w.getAttributes(), "decision1.key", new GroupPrincipal( "Admin" ) );
+        d2 = new SimpleDecision( w.getId(), w.getAttributes(), "decision2.key", new WikiPrincipal( "Owner2" ) );
+        d3 = new SimpleDecision( w.getId(), w.getAttributes(), "decision3.key", janneSession.getUserPrincipal() );
+        m_queue.add( d1 );
+        m_queue.add( d2 );
+        m_queue.add( d3 );
     }
 
     @Test
@@ -150,38 +150,37 @@
     }
 
     @Test
-    public void testDecisionWorkflow() throws WikiException
-    {
+    public void testDecisionWorkflow() throws WikiException {
         final Principal janne = janneSession.getUserPrincipal();
 
         // Clean out the queue first
-        m_queue.remove(d1);
-        m_queue.remove(d2);
-        m_queue.remove(d3);
+        m_queue.remove( d1 );
+        m_queue.remove( d2 );
+        m_queue.remove( d3 );
 
         // Create a workflow with 3 steps, with a Decision for Janne in the middle
-        w = new Workflow("workflow.key", new WikiPrincipal("Owner1"));
-        final Step startTask = new TaskTest.NormalTask(w);
-        final Step endTask = new TaskTest.NormalTask(w);
-        final Decision decision = new SimpleDecision(w, "decision.Actor1Decision", janne);
-        startTask.addSuccessor(Outcome.STEP_COMPLETE, decision);
-        decision.addSuccessor(Outcome.DECISION_APPROVE, endTask);
-        w.setFirstStep(startTask);
+        w = new Workflow( "workflow.key", new WikiPrincipal( "Owner1" ) );
+        final Step startTask = new TaskTest.NormalTask( w );
+        final Step endTask = new TaskTest.NormalTask( w );
+        final Decision decision = new SimpleDecision( w.getId(), w.getAttributes(), "decision.Actor1Decision", janne );
+        startTask.addSuccessor( Outcome.STEP_COMPLETE, decision );
+        decision.addSuccessor( Outcome.DECISION_APPROVE, endTask );
+        w.setFirstStep( startTask );
 
         // Start the workflow, and verify that the Decision is the current Step
         w.start();
-        Assertions.assertEquals(decision, w.getCurrentStep());
+        Assertions.assertEquals( decision, w.getCurrentStep() );
 
         // Verify that it's also in Janne's DecisionQueue
-        Collection< Decision > decisions = m_queue.getActorDecisions(janneSession);
-        Assertions.assertEquals(1, decisions.size());
-        final Decision d = (Decision)decisions.iterator().next();
-        Assertions.assertEquals(decision, d);
+        Collection< Decision > decisions = m_queue.getActorDecisions( janneSession );
+        Assertions.assertEquals( 1, decisions.size() );
+        final Decision d = ( Decision )decisions.iterator().next();
+        Assertions.assertEquals( decision, d );
 
         // Make Decision, and verify that it's gone from the queue
-        m_queue.decide(decision, Outcome.DECISION_APPROVE);
-        decisions = m_queue.getActorDecisions(janneSession);
-        Assertions.assertEquals(0, decisions.size());
+        m_queue.decide( decision, Outcome.DECISION_APPROVE );
+        decisions = m_queue.getActorDecisions( janneSession );
+        Assertions.assertEquals( 0, decisions.size() );
     }
 
 }
diff --git a/jspwiki-main/src/test/java/org/apache/wiki/workflow/SimpleDecisionTest.java b/jspwiki-main/src/test/java/org/apache/wiki/workflow/SimpleDecisionTest.java
index 5bf3539..6cd4e86 100644
--- a/jspwiki-main/src/test/java/org/apache/wiki/workflow/SimpleDecisionTest.java
+++ b/jspwiki-main/src/test/java/org/apache/wiki/workflow/SimpleDecisionTest.java
@@ -36,7 +36,7 @@
     @BeforeEach
     public void setUp() throws Exception {
         m_workflow = new Workflow( "workflow.key", new WikiPrincipal( "Owner1" ) );
-        m_decision = new SimpleDecision( m_workflow, "decision.key", new WikiPrincipal( "Actor1" ) );
+        m_decision = new SimpleDecision( m_workflow.getId(), m_workflow.getAttributes(), "decision.key", new WikiPrincipal( "Actor1" ) );
     }
 
     @Test
@@ -79,11 +79,11 @@
     @Test
     public void testSuccessors() {
         // If the decision is approved, branch to another decision (d2)
-        final Step d2 = new SimpleDecision( m_workflow, "decision2.key", new WikiPrincipal( "Actor1" ) );
+        final Step d2 = new SimpleDecision( m_workflow.getId(), m_workflow.getAttributes(), "decision2.key", new WikiPrincipal( "Actor1" ) );
         m_decision.addSuccessor( Outcome.DECISION_APPROVE, d2 );
 
         // If the decision is denied, branch to another decision (d3)
-        final Step d3 = new SimpleDecision( m_workflow, "decision3.key", new WikiPrincipal( "Actor1" ) );
+        final Step d3 = new SimpleDecision( m_workflow.getId(), m_workflow.getAttributes(), "decision3.key", new WikiPrincipal( "Actor1" ) );
         m_decision.addSuccessor( Outcome.DECISION_DENY, d3 );
 
         Assertions.assertEquals( d2, m_decision.getSuccessor( Outcome.DECISION_APPROVE ) );
@@ -148,7 +148,7 @@
 
     @Test
     public void testGetWorkflow() {
-        Assertions.assertEquals( m_workflow, m_decision.getWorkflow() );
+        Assertions.assertEquals( m_workflow.getId(), m_decision.getWorkflowId() );
     }
 
     @Test
diff --git a/jspwiki-main/src/test/java/org/apache/wiki/workflow/TaskTest.java b/jspwiki-main/src/test/java/org/apache/wiki/workflow/TaskTest.java
index 6e330c0..53612e9 100644
--- a/jspwiki-main/src/test/java/org/apache/wiki/workflow/TaskTest.java
+++ b/jspwiki-main/src/test/java/org/apache/wiki/workflow/TaskTest.java
@@ -40,7 +40,7 @@
 
         public NormalTask( final Workflow workflow)
         {
-            super(workflow, "task.normal");
+            super(workflow.getId(), workflow.getAttributes(), "task.normal");
         }
         public Outcome execute() throws WikiException
         {
@@ -56,7 +56,7 @@
 
         public ErrorTask( final Workflow workflow)
         {
-            super(workflow, "task.error");
+            super(workflow.getId(), workflow.getAttributes(), "task.error");
         }
         public Outcome execute() throws WikiException
         {
@@ -86,11 +86,11 @@
     public void testSuccessors()
     {
         // If task finishes normally, branch to a decision (d1)
-        final Step d1 = new SimpleDecision(m_workflow, "decision1.key", new WikiPrincipal("Actor1"));
+        final Step d1 = new SimpleDecision(m_workflow.getId(), m_workflow.getAttributes(), "decision1.key", new WikiPrincipal("Actor1"));
         m_task.addSuccessor(Outcome.STEP_COMPLETE, d1);
 
         // If the task aborts, branch to an alternate decision (d2)
-        final Step d2 = new SimpleDecision(m_workflow, "decision2.key", new WikiPrincipal("Actor2"));
+        final Step d2 = new SimpleDecision(m_workflow.getId(), m_workflow.getAttributes(), "decision2.key", new WikiPrincipal("Actor2"));
         m_task.addSuccessor(Outcome.STEP_ABORT, d2);
 
         Assertions.assertEquals(d1, m_task.getSuccessor(Outcome.STEP_COMPLETE));
@@ -171,7 +171,7 @@
     @Test
     public void testGetWorkflow()
     {
-        Assertions.assertEquals(m_workflow, m_task.getWorkflow());
+        Assertions.assertEquals(m_workflow.getId(), m_task.getWorkflowId() );
     }
 
     @Test
diff --git a/jspwiki-main/src/test/java/org/apache/wiki/workflow/WorkflowManagerTest.java b/jspwiki-main/src/test/java/org/apache/wiki/workflow/WorkflowManagerTest.java
index 2597ca9..8975859 100644
--- a/jspwiki-main/src/test/java/org/apache/wiki/workflow/WorkflowManagerTest.java
+++ b/jspwiki-main/src/test/java/org/apache/wiki/workflow/WorkflowManagerTest.java
@@ -41,7 +41,7 @@
         w = new Workflow( "workflow.key", new WikiPrincipal( "Owner1" ) );
         final Step startTask = new TaskTest.NormalTask( w );
         final Step endTask = new TaskTest.NormalTask( w );
-        final Decision decision = new SimpleDecision( w, "decision.editWikiApproval", new WikiPrincipal( "Actor1" ) );
+        final Decision decision = new SimpleDecision( w.getId(), w.getAttributes(), "decision.editWikiApproval", new WikiPrincipal( "Actor1" ) );
         startTask.addSuccessor( Outcome.STEP_COMPLETE, decision );
         decision.addSuccessor( Outcome.DECISION_APPROVE, endTask );
         w.setFirstStep( startTask );
@@ -52,9 +52,7 @@
 
     @Test
     public void testStart() throws WikiException {
-        // Once we start the workflow, it should show that it's started
-        // and the WM should have assigned it an ID
-        Assertions.assertEquals( Workflow.ID_NOT_SET, w.getId() );
+        // Once we start the workflow, it should show that it's started and the WM should have assigned it an ID
         Assertions.assertFalse( w.isStarted() );
         wm.start( w );
         Assertions.assertNotEquals( Workflow.ID_NOT_SET, w.getId() );
@@ -63,17 +61,16 @@
 
     @Test
     public void testWorkflows() throws WikiException {
-        // There should be no workflows in the cache, and none in completed list
-        Assertions.assertEquals( 0, wm.getWorkflows().size() );
+        // After Workflow being created, it gets added to the workflows cache, there should be none in the completed list
+        Assertions.assertEquals( 1, wm.getWorkflows().size() );
         Assertions.assertEquals( 0, wm.getCompletedWorkflows().size() );
 
-        // After starting, there should be 1 in the cache, with ID=1
+        // After starting, there should be 1 in the cache
         wm.start( w );
         Assertions.assertEquals( 1, wm.getWorkflows().size() );
         Assertions.assertEquals( 0, wm.getCompletedWorkflows().size() );
         final Workflow workflow = wm.getWorkflows().iterator().next();
         Assertions.assertEquals( w, workflow );
-        Assertions.assertEquals( 1, workflow.getId() );
 
         // After forcing a decision on step 2, the workflow should complete and vanish from the cache
         final Decision d = ( Decision )w.getCurrentStep();
diff --git a/jspwiki-main/src/test/java/org/apache/wiki/workflow/WorkflowTest.java b/jspwiki-main/src/test/java/org/apache/wiki/workflow/WorkflowTest.java
index 84b7444..6064c28 100644
--- a/jspwiki-main/src/test/java/org/apache/wiki/workflow/WorkflowTest.java
+++ b/jspwiki-main/src/test/java/org/apache/wiki/workflow/WorkflowTest.java
@@ -32,6 +32,8 @@
 
 public class WorkflowTest {
 
+    WorkflowManager workflowsEventListener;
+
     Workflow w;
 
     Task initTask;
@@ -44,6 +46,9 @@
 
     @BeforeEach
     public void setUp() throws Exception {
+        // listen to workflow events
+        workflowsEventListener = new DefaultWorkflowManager();
+
         // Create workflow; owner is test user
         w = new Workflow( "workflow.myworkflow", new WikiPrincipal( "Owner1" ) );
 
@@ -55,7 +60,7 @@
 
         // Create an intermetidate decision step
         final Principal actor = new GroupPrincipal( "Admin" );
-        decision = new SimpleDecision( w, "decision.AdminDecision", actor );
+        decision = new SimpleDecision( w.getId(), w.getAttributes(), "decision.AdminDecision", actor );
 
         // Hook the steps together
         initTask.addSuccessor( Outcome.STEP_COMPLETE, decision );
@@ -74,7 +79,6 @@
         Assertions.assertNull( w.getCurrentStep() );
         Assertions.assertNull( w.getCurrentActor() );
         Assertions.assertEquals( 0, w.getHistory().size() );
-        Assertions.assertEquals( Workflow.ID_NOT_SET, w.getId() );
         Assertions.assertEquals( new WikiPrincipal( "Owner1" ), w.getOwner() );
         Assertions.assertEquals( Workflow.CREATED, w.getCurrentState() );
         Assertions.assertEquals( Step.TIME_NOT_SET, w.getStartTime() );
@@ -355,7 +359,6 @@
 
     @Test
     public void testSetId() {
-        Assertions.assertEquals( Workflow.ID_NOT_SET, w.getId() );
         w.setId( 1001 );
         Assertions.assertEquals( 1001, w.getId() );
     }