You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by ju...@apache.org on 2020/09/30 20:39:03 UTC

[jspwiki] 06/08: JSPWIKI-1131: tasks don't store contexts anymore they receive them through the execute(..) method

This is an automated email from the ASF dual-hosted git repository.

juanpablo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jspwiki.git

commit f582ecd5fdae98924de659b35b12faf8b51b6ec7
Author: juanpablo <ju...@apache.org>
AuthorDate: Wed Sep 30 22:37:02 2020 +0200

    JSPWIKI-1131: tasks don't store contexts anymore they receive them through the execute(..) method
    
    Context must be carried around through workflow, tasks and classes using them
---
 .../org/apache/wiki/auth/DefaultUserManager.java   | 13 ++--
 .../java/org/apache/wiki/auth/SessionMonitor.java  | 64 +++++++++++++++---
 .../java/org/apache/wiki/auth/UserManager.java     |  6 +-
 .../java/org/apache/wiki/filters/CreoleFilter.java |  4 +-
 .../org/apache/wiki/pages/DefaultPageManager.java  |  6 +-
 .../org/apache/wiki/tasks/DefaultTasksManager.java | 14 ++--
 .../java/org/apache/wiki/tasks/TasksManager.java   | 11 +--
 .../wiki/tasks/auth/SaveUserProfileTask.java       | 21 +++---
 .../wiki/tasks/pages/PreSaveWikiPageTask.java      | 23 ++-----
 .../apache/wiki/tasks/pages/SaveWikiPageTask.java  | 21 +++---
 .../org/apache/wiki/workflow/AbstractStep.java     | 12 +---
 .../java/org/apache/wiki/workflow/Decision.java    | 12 ++--
 .../org/apache/wiki/workflow/DecisionQueue.java    |  7 +-
 .../wiki/workflow/DefaultWorkflowManager.java      | 23 ++-----
 .../apache/wiki/workflow/SimpleNotification.java   |  7 +-
 .../main/java/org/apache/wiki/workflow/Step.java   |  6 +-
 .../java/org/apache/wiki/workflow/Workflow.java    | 58 ++++++++--------
 .../java/org/apache/wiki/auth/UserManagerTest.java | 39 ++++++-----
 .../wiki/auth/authorize/GroupManagerTest.java      |  2 +-
 .../apache/wiki/workflow/ApprovalWorkflowTest.java | 20 +++---
 .../apache/wiki/workflow/DecisionQueueTest.java    | 10 +--
 .../apache/wiki/workflow/SimpleDecisionTest.java   |  8 +--
 .../java/org/apache/wiki/workflow/TaskTest.java    | 15 ++--
 .../apache/wiki/workflow/WorkflowManagerTest.java  |  8 +--
 .../org/apache/wiki/workflow/WorkflowTest.java     | 79 ++++++++++------------
 25 files changed, 248 insertions(+), 241 deletions(-)

diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java
index 605994c..a6078ef 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java
@@ -179,7 +179,8 @@ public class DefaultUserManager implements UserManager {
 
     /** {@inheritDoc} */
     @Override
-    public void setUserProfile( final Session session, final UserProfile profile ) throws DuplicateUserException, WikiException {
+    public void setUserProfile( final Context context, final UserProfile profile ) throws DuplicateUserException, WikiException {
+        final Session session = context.getWikiSession();
         // Verify user is allowed to save profile!
         final Permission p = new WikiPermission( m_engine.getApplicationName(), WikiPermission.EDIT_PROFILE_ACTION );
         if ( !m_engine.getManager( AuthorizationManager.class ).checkPermission( session, p ) ) {
@@ -212,7 +213,7 @@ public class DefaultUserManager implements UserManager {
 
         // For new accounts, create approval workflow for user profile save.
         if( newProfile && oldProfile != null && oldProfile.isNew() ) {
-            startUserProfileCreationWorkflow( session, profile );
+            startUserProfileCreationWorkflow( context, profile );
 
             // If the profile doesn't need approval, then just log the user in
 
@@ -250,10 +251,10 @@ public class DefaultUserManager implements UserManager {
 
     /** {@inheritDoc} */
     @Override
-    public void startUserProfileCreationWorkflow( final Session session, final UserProfile profile ) throws WikiException {
+    public void startUserProfileCreationWorkflow( final Context context, final UserProfile profile ) throws WikiException {
         final WorkflowBuilder builder = WorkflowBuilder.getBuilder( m_engine );
-        final Principal submitter = session.getUserPrincipal();
-        final Step completionTask = m_engine.getManager( TasksManager.class ).buildSaveUserProfileTask( m_engine, session.getLocale() );
+        final Principal submitter = context.getWikiSession().getUserPrincipal();
+        final Step completionTask = m_engine.getManager( TasksManager.class ).buildSaveUserProfileTask( context.getWikiSession().getLocale() );
 
         // Add user profile attribute as Facts for the approver (if required)
         final boolean hasEmail = profile.getEmail() != null;
@@ -273,7 +274,7 @@ public class DefaultUserManager implements UserManager {
                                                                  null );
 
         workflow.setAttribute( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE, profile );
-        workflow.start();
+        workflow.start( context );
 
         final boolean approvalRequired = workflow.getCurrentStep() instanceof Decision;
 
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java
index 074cc3d..126a377 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/SessionMonitor.java
@@ -49,7 +49,7 @@ public class SessionMonitor implements HttpSessionListener {
     private static final Logger log = Logger.getLogger( SessionMonitor.class );
 
     /** Map with Engines as keys, and SessionMonitors as values. */
-    private static ConcurrentHashMap< Engine, SessionMonitor > c_monitors = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap< Engine, SessionMonitor > c_monitors = new ConcurrentHashMap<>();
 
     /** Weak hashmap with HttpSessions as keys, and WikiSessions as values. */
     private final Map< String, Session > m_sessions = new WeakHashMap<>();
@@ -94,8 +94,21 @@ public class SessionMonitor implements HttpSessionListener {
      *  @return the WikiSession, if found
      */
     private Session findSession( final HttpSession session ) {
-        Session wikiSession = null;
         final String sid = ( session == null ) ? "(null)" : session.getId();
+        return findSession( sid );
+    }
+
+    /**
+     *  Just looks for a WikiSession; does not create a new one.
+     * This method may return <code>null</code>, <em>and
+     * callers should check for this value</em>.
+     *
+     *  @param sessionId the user's HTTP session id
+     *  @return the WikiSession, if found
+     */
+    private Session findSession( final String sessionId ) {
+        Session wikiSession = null;
+        final String sid = ( sessionId == null ) ? "(null)" : sessionId;
         final Session storedSession = m_sessions.get( sid );
 
         // If the weak reference returns a wiki session, return it
@@ -108,6 +121,7 @@ public class SessionMonitor implements HttpSessionListener {
 
         return wikiSession;
     }
+
     /**
      * <p>Looks up the wiki session associated with a user's Http session and adds it to the session cache. This method will return the
      * "guest session" as constructed by {@link org.apache.wiki.api.spi.SessionSPI#guest(Engine)} if the HttpSession is not currently
@@ -119,24 +133,52 @@ public class SessionMonitor implements HttpSessionListener {
      * @return the wiki session
      */
     public final Session find( final HttpSession session ) {
-        Session wikiSession = findSession( session );
+        final Session wikiSession = findSession( session );
         final String sid = ( session == null ) ? "(null)" : session.getId();
+        if( wikiSession == null ) {
+            return createGuestSessionFor( sid );
+        }
 
-        // Otherwise, create a new guest session and stash it.
+        return wikiSession;
+    }
+
+    /**
+     * <p>Looks up the wiki session associated with a user's Http session and adds it to the session cache. This method will return the
+     * "guest session" as constructed by {@link org.apache.wiki.api.spi.SessionSPI#guest(Engine)} if the HttpSession is not currently
+     * associated with a WikiSession. This method is guaranteed to return a non-<code>null</code> WikiSession.</p>
+     * <p>Internally, the session is stored in a HashMap; keys are the HttpSession objects, while the values are
+     * {@link java.lang.ref.WeakReference}-wrapped WikiSessions.</p>
+     *
+     * @param sessionId the HTTP session
+     * @return the wiki session
+     */
+    public final Session find( final String sessionId ) {
+        final Session wikiSession = findSession( sessionId );
         if( wikiSession == null ) {
-            if( log.isDebugEnabled() ) {
-                log.debug( "Looking up WikiSession for session ID=" + sid + "... not found. Creating guestSession()" );
-            }
-            wikiSession = Wiki.session().guest( m_engine );
-            synchronized( m_sessions ) {
-                m_sessions.put( sid, wikiSession );
-            }
+            return createGuestSessionFor( sessionId );
         }
 
         return wikiSession;
     }
 
     /**
+     * Creates a new session and stashes it
+     *
+     * @param sessionId id looked for before creating the guest session
+     * @return a new guest session
+     */
+    private Session createGuestSessionFor( final String sessionId ) {
+        if( log.isDebugEnabled() ) {
+            log.debug( "Session for session ID=" + sessionId + "... not found. Creating guestSession()" );
+        }
+        final Session wikiSession = Wiki.session().guest( m_engine );
+        synchronized( m_sessions ) {
+            m_sessions.put( sessionId, wikiSession );
+        }
+        return wikiSession;
+    }
+
+    /**
      * Removes the wiki session associated with the user's HttpRequest from the session cache.
      *
      * @param request the user's HTTP request
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/UserManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/UserManager.java
index 953f811..d58157f 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/auth/UserManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/UserManager.java
@@ -90,7 +90,7 @@ public interface UserManager extends Initializable {
      * correct Principals to be reloaded into the current Session's Subject.
      * </p>
      *
-     * @param session the wiki session, which may not be <code>null</code>
+     * @param context the wiki context, which may not be <code>null</code>
      * @param profile the user profile, which may not be <code>null</code>
      * @throws DuplicateUserException if the proposed profile's login name or full name collides with another
      * @throws WikiException if the save fails for some reason. If the current user does not have
@@ -99,9 +99,9 @@ public interface UserManager extends Initializable {
      * {@link org.apache.wiki.workflow.DecisionRequiredException}. All other WikiException
      * indicate a condition that is not normal is probably due to mis-configuration
      */
-    void setUserProfile( Session session, UserProfile profile ) throws DuplicateUserException, WikiException;
+    void setUserProfile( Context context, UserProfile profile ) throws DuplicateUserException, WikiException;
 
-    void startUserProfileCreationWorkflow( Session session, UserProfile profile ) throws WikiException;
+    void startUserProfileCreationWorkflow( Context context, UserProfile profile ) throws WikiException;
 
     /**
      * <p> Extracts user profile parameters from the HTTP request and populates a UserProfile with them. The UserProfile will either be a
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/filters/CreoleFilter.java b/jspwiki-main/src/main/java/org/apache/wiki/filters/CreoleFilter.java
index 566959f..b269e6c 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/filters/CreoleFilter.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/filters/CreoleFilter.java
@@ -60,7 +60,7 @@ public class CreoleFilter extends BasePageFilter {
     public String preSave( final Context wikiContext, final String content ) {
         try {
             final String username = wikiContext.getCurrentUser().getName();
-            final Properties prop = wikiContext.getEngine().getWikiProperties();
+            final Properties prop = m_engine.getWikiProperties();
             return new CreoleToJSPWikiTranslator().translateSignature(prop, content,username);
         } catch( final Exception e ) {
             log.error( e.getMessage(), e );
@@ -74,7 +74,7 @@ public class CreoleFilter extends BasePageFilter {
     @Override
     public String preTranslate( final Context wikiContext, final String content ) {
         try {
-            final Properties prop = wikiContext.getEngine().getWikiProperties();
+            final Properties prop = m_engine.getWikiProperties();
             return new CreoleToJSPWikiTranslator().translate(prop ,content);
         } catch( final Exception e ) {
             log.error( e.getMessage(), e );
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/pages/DefaultPageManager.java b/jspwiki-main/src/main/java/org/apache/wiki/pages/DefaultPageManager.java
index 3a318bb..3802b39 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/pages/DefaultPageManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/pages/DefaultPageManager.java
@@ -246,8 +246,8 @@ public class DefaultPageManager implements PageManager {
         // messages will appear in his/her workflow inbox.
         final WorkflowBuilder builder = WorkflowBuilder.getBuilder( m_engine );
         final Principal submitter = context.getCurrentUser();
-        final Step prepTask = m_engine.getManager( TasksManager.class ).buildPreSaveWikiPageTask( context, proposedText );
-        final Step completionTask = m_engine.getManager( TasksManager.class ).buildSaveWikiPageTask( context );
+        final Step prepTask = m_engine.getManager( TasksManager.class ).buildPreSaveWikiPageTask( proposedText );
+        final Step completionTask = m_engine.getManager( TasksManager.class ).buildSaveWikiPageTask();
         final String diffText = m_engine.getManager( DifferenceManager.class ).makeDiff( context, oldText, proposedText );
         final boolean isAuthenticated = context.getWikiSession().isAuthenticated();
         final Fact[] facts = new Fact[ 5 ];
@@ -264,7 +264,7 @@ public class DefaultPageManager implements PageManager {
                                                                  facts,
                                                                  completionTask,
                                                                  rejectKey );
-        workflow.start();
+        workflow.start( context );
 
         // Let callers know if the page-save requires approval
         if ( workflow.getCurrentStep() instanceof Decision ) {
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/tasks/DefaultTasksManager.java b/jspwiki-main/src/main/java/org/apache/wiki/tasks/DefaultTasksManager.java
index 7d45562..4b6dece 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/tasks/DefaultTasksManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/tasks/DefaultTasksManager.java
@@ -18,8 +18,6 @@
  */
 package org.apache.wiki.tasks;
 
-import org.apache.wiki.api.core.Context;
-import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.tasks.auth.SaveUserProfileTask;
 import org.apache.wiki.tasks.pages.PreSaveWikiPageTask;
 import org.apache.wiki.tasks.pages.SaveWikiPageTask;
@@ -37,24 +35,24 @@ public class DefaultTasksManager implements TasksManager {
      * {@inheritDoc}
      */
     @Override
-    public Step buildPreSaveWikiPageTask( final Context context, final String proposedText ) {
-        return new PreSaveWikiPageTask( context, proposedText );
+    public Step buildPreSaveWikiPageTask( final String proposedText ) {
+        return new PreSaveWikiPageTask( proposedText );
     }
     
     /**
      * {@inheritDoc}
      */
     @Override
-    public Step buildSaveWikiPageTask( final Context context ) {
-        return new SaveWikiPageTask( context );
+    public Step buildSaveWikiPageTask() {
+        return new SaveWikiPageTask();
     }
     
     /**
      * {@inheritDoc}
      */
     @Override
-    public Step buildSaveUserProfileTask( final Engine engine, final Locale loc ) {
-        return new SaveUserProfileTask( engine, loc );
+    public Step buildSaveUserProfileTask( final Locale loc ) {
+        return new SaveUserProfileTask( loc );
     }
     
 }
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/tasks/TasksManager.java b/jspwiki-main/src/main/java/org/apache/wiki/tasks/TasksManager.java
index 3c56f87..6657488 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/tasks/TasksManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/tasks/TasksManager.java
@@ -18,8 +18,6 @@
  */
 package org.apache.wiki.tasks;
 
-import org.apache.wiki.api.core.Context;
-import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.workflow.Step;
 
 import java.security.Principal;
@@ -44,27 +42,24 @@ public interface TasksManager {
     /**
      * Builds a pre-save WikiPage task.
      * 
-     * @param context associated wiki context.
      * @param proposedText text proposed to be saved on the wiki page.
      * @return a pre-save WikiPage task.
      */
-    Step buildPreSaveWikiPageTask( Context context, String proposedText );
+    Step buildPreSaveWikiPageTask( String proposedText );
     
     /**
      * Builds a save WikiPage task.
      *
-     * @param context associated wiki context.
      * @return a save WikiPage task.
      */
-    Step buildSaveWikiPageTask( Context context );
+    Step buildSaveWikiPageTask();
     
     /**
      * Builds a save user profile task.
      * 
-     * @param engine associated wiki context.
      * @param loc text proposed to be saved on the wiki page.
      * @return a save user profile task.
      */
-    Step buildSaveUserProfileTask( Engine engine, Locale loc );
+    Step buildSaveUserProfileTask( Locale loc );
     
 }
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 fd6d413..7bb55db 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
@@ -1,8 +1,8 @@
 package org.apache.wiki.tasks.auth;
 
 import org.apache.log4j.Logger;
+import org.apache.wiki.api.core.Context;
 import org.apache.wiki.api.core.ContextEnum;
-import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.api.exceptions.WikiException;
 import org.apache.wiki.auth.UserManager;
 import org.apache.wiki.auth.user.UserProfile;
@@ -25,17 +25,13 @@ public class SaveUserProfileTask extends Task {
 
     private static final long serialVersionUID = 6994297086560480285L;
     private static final Logger LOG = Logger.getLogger( SaveUserProfileTask.class );
-    private final Engine m_engine;
     private final Locale m_loc;
 
     /**
      * Constructs a new Task for saving a user profile.
-     *
-     * @param engine the wiki engine
      */
-    public SaveUserProfileTask( final Engine engine, final Locale loc ) {
+    public SaveUserProfileTask( final Locale loc ) {
         super( TasksManager.USER_PROFILE_SAVE_TASK_MESSAGE_KEY );
-        m_engine = engine;
         m_loc = loc;
     }
 
@@ -46,29 +42,30 @@ public class SaveUserProfileTask extends Task {
      * @throws WikiException if the save did not complete for some reason
      */
     @Override
-    public Outcome execute() throws WikiException {
+    public Outcome execute( final Context context ) throws WikiException {
         // Retrieve user 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 );
+        context.getEngine().getManager( UserManager.class ).getUserDatabase().save( profile );
 
         // Send e-mail if user supplied an e-mail address
         if ( profile != null && profile.getEmail() != null ) {
             try {
-                final InternationalizationManager i18n = m_engine.getManager( InternationalizationManager.class );
-                final String app = m_engine.getApplicationName();
+                final InternationalizationManager i18n = context.getEngine().getManager( InternationalizationManager.class );
+                final String app = context.getEngine().getApplicationName();
                 final String to = profile.getEmail();
                 final String subject = i18n.get( InternationalizationManager.DEF_TEMPLATE, m_loc,
                                                  "notification.createUserProfile.accept.subject", app );
 
+                final String loginUrl = context.getEngine().getURL( ContextEnum.WIKI_LOGIN.getRequestContext(), null, null );
                 final String content = i18n.get( InternationalizationManager.DEF_TEMPLATE, m_loc,
                                                  "notification.createUserProfile.accept.content", app,
                                                  profile.getLoginName(),
                                                  profile.getFullname(),
                                                  profile.getEmail(),
-                                                 m_engine.getURL( ContextEnum.WIKI_LOGIN.getRequestContext(), null, null ) );
-                MailUtil.sendMessage( m_engine.getWikiProperties(), to, subject, content );
+                                                 loginUrl );
+                MailUtil.sendMessage( context.getEngine().getWikiProperties(), to, subject, content );
             } catch ( final AddressException e) {
                 LOG.debug( e.getMessage(), e );
             } catch ( final MessagingException me ) {
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 8aeca27..55f374f 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
@@ -27,29 +27,24 @@ import org.apache.wiki.workflow.Outcome;
 import org.apache.wiki.workflow.Task;
 import org.apache.wiki.workflow.WorkflowManager;
 
-import java.security.Principal;
-
 
 /**
  * Handles the page pre-save actions. If the proposed page text is the same as the current version, 
- * the {@link #execute()} method returns {@link org.apache.wiki.workflow.Outcome#STEP_ABORT}. Any
+ * the {@link #execute( Context )} method returns {@link org.apache.wiki.workflow.Outcome#STEP_ABORT}. Any
  * WikiExceptions thrown by page filters will be re-thrown, and the workflow will abort.
  */
 public class PreSaveWikiPageTask extends Task {
 
     private static final long serialVersionUID = 6304715570092804615L;
-    private final Context m_context;
     private final String m_proposedText;
 
     /**
      * Creates the task.
      *
-     * @param context The WikiContext
      * @param proposedText The text that was just saved.
      */
-    public PreSaveWikiPageTask( final Context context, final String proposedText ) {
+    public PreSaveWikiPageTask( final String proposedText ) {
         super( TasksManager.WIKIPAGE_PRESAVE_TASK_MESSAGE_KEY );
-        m_context = context;
         m_proposedText = proposedText;
     }
 
@@ -57,20 +52,16 @@ public class PreSaveWikiPageTask extends Task {
      * {@inheritDoc}
      */
     @Override
-    public Outcome execute() throws WikiException {
+    public Outcome execute( final Context context ) throws WikiException {
         // Get the wiki page
-        final Page page = m_context.getPage();
-
+        final Page page = context.getPage();
         // Figure out who the author was. Prefer the author set programmatically; otherwise get from the current logged in user
-        if( page.getAuthor() == null ) {
-            final Principal wup = m_context.getCurrentUser();
-            if( wup != null ) {
-                page.setAuthor( wup.getName() );
-            }
+        if( context.getPage().getAuthor() == null && context.getCurrentUser() != null ) {
+            page.setAuthor( context.getCurrentUser().getName() );
         }
 
         // Run the pre-save filters. If any exceptions, add error to list, abort, and redirect
-        final String saveText = m_context.getEngine().getManager( FilterManager.class ).doPreSaveFiltering(m_context, m_proposedText);
+        final String saveText = context.getEngine().getManager( FilterManager.class ).doPreSaveFiltering( context, m_proposedText );
 
         // Stash the wiki context, old and new text as workflow attributes
         getWorkflowContext().put( WorkflowManager.WF_WP_SAVE_FACT_PROPOSED_TEXT, saveText );
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 53ce0b0..4ef9f23 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
@@ -19,12 +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.pages.PageManager;
 import org.apache.wiki.render.RenderingManager;
+import org.apache.wiki.search.SearchManager;
 import org.apache.wiki.tasks.TasksManager;
 import org.apache.wiki.workflow.Outcome;
 import org.apache.wiki.workflow.Task;
@@ -38,34 +38,33 @@ public class SaveWikiPageTask extends Task {
 
     private static final long serialVersionUID = 3190559953484411420L;
 
-    final Context context;
-
     /**
      * Creates the Task.
      */
-    public SaveWikiPageTask( final Context context ) {
+    public SaveWikiPageTask() {
         super( TasksManager.WIKIPAGE_SAVE_TASK_MESSAGE_KEY );
-        this.context = context;
     }
 
     /**
      * {@inheritDoc}
      */
     @Override
-    public Outcome execute() throws WikiException {
+    public Outcome execute( final Context context ) throws WikiException {
         // Retrieve attributes
         final String proposedText = ( String )getWorkflowContext().get( WorkflowManager.WF_WP_SAVE_FACT_PROPOSED_TEXT );
 
-        final Engine engine = context.getEngine();
         final Page page = context.getPage();
 
         // Let the rest of the engine handle actual saving.
-        engine.getManager( PageManager.class ).putPageText( page, proposedText );
+        context.getEngine().getManager( PageManager.class ).putPageText( page, proposedText );
 
         // Refresh the context for post save filtering.
-        engine.getManager( PageManager.class ).getPage( page.getName() );
-        engine.getManager( RenderingManager.class ).textToHTML( context, proposedText );
-        engine.getManager( FilterManager.class ).doPostSaveFiltering( context, proposedText );
+        context.getEngine().getManager( PageManager.class ).getPage( page.getName() );
+        context.getEngine().getManager( RenderingManager.class ).textToHTML( context, proposedText );
+        context.getEngine().getManager( FilterManager.class ).doPostSaveFiltering( context, proposedText );
+
+        // Reindex saved page
+        context.getEngine().getManager(SearchManager.class ).reindexPage( page );
 
         return Outcome.STEP_COMPLETE;
     }
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 ca575dd..d4d16b6 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
@@ -18,18 +18,12 @@
  */
 package org.apache.wiki.workflow;
 
+import org.apache.wiki.api.core.Context;
 import org.apache.wiki.api.exceptions.WikiException;
 
 import java.io.Serializable;
 import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 /**
  * Abstract superclass that provides a complete implementation of most Step methods; subclasses need only implement {@link #execute()} and
@@ -120,7 +114,7 @@ public abstract class AbstractStep implements Step {
     /**
      * {@inheritDoc}
      */
-    public abstract Outcome execute() throws WikiException;
+    public abstract Outcome execute( Context ctx ) throws WikiException;
 
     /**
      * {@inheritDoc}
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 a4e145c..6777f28 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
@@ -18,6 +18,7 @@
  */
 package org.apache.wiki.workflow;
 
+import org.apache.wiki.api.core.Context;
 import org.apache.wiki.api.exceptions.WikiException;
 import org.apache.wiki.event.WikiEventEmitter;
 import org.apache.wiki.event.WorkflowEvent;
@@ -33,7 +34,7 @@ import java.util.Map;
 /**
  * <p>
  * AbstractStep subclass that asks an actor Principal to choose an Outcome on behalf of an owner (also a Principal). The actor
- * "makes the decision" by calling the {@link #decide(Outcome)} method. When this method is called, it will set the Decision's Outcome to
+ * "makes the decision" by calling the {@link #decide(Outcome, Context)} method. When this method is called, it will set the Decision's Outcome to
  * the one supplied. If the parent Workflow is in the {@link Workflow#WAITING} state, it will be re-started. Any checked WikiExceptions
  * thrown by the workflow after re-start will be re-thrown to callers.
  * </p>
@@ -104,22 +105,23 @@ public abstract class Decision extends AbstractStep {
      * </p>
      * 
      * @param outcome the Outcome of the Decision
+     * @param context wiki context of the Decision
      * @throws WikiException if the act of restarting the Workflow throws an exception
      */
-    public void decide( final Outcome outcome ) throws WikiException {
+    public void decide( final Outcome outcome, final Context context ) throws WikiException {
         super.setOutcome( outcome );
-        WikiEventEmitter.fireWorkflowEvent( this, WorkflowEvent.DQ_REMOVAL );
+        WikiEventEmitter.fireWorkflowEvent( this, WorkflowEvent.DQ_REMOVAL, context );
     }
 
     /**
      * Default implementation that always returns {@link Outcome#STEP_CONTINUE} if the current Outcome isn't a completion (which will be
-     * true if the {@link #decide(Outcome)} method hasn't been executed yet. This method will also add the Decision to the associated
+     * true if the {@link #decide(Outcome, Context)} method hasn't been executed yet. This method will also add the Decision to the associated
      * DecisionQueue.
      * 
      * @return the Outcome of the execution
      * @throws WikiException never
      */
-    public Outcome execute() throws WikiException {
+    public Outcome execute( final Context context ) throws WikiException {
         if( getOutcome().isCompletion() ) {
             return getOutcome();
         }
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/DecisionQueue.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/DecisionQueue.java
index a3fc33e..eba4419 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/DecisionQueue.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/DecisionQueue.java
@@ -18,6 +18,7 @@
  */
 package org.apache.wiki.workflow;
 
+import org.apache.wiki.api.core.Context;
 import org.apache.wiki.api.core.Session;
 import org.apache.wiki.api.exceptions.WikiException;
 import org.apache.wiki.event.WikiEventEmitter;
@@ -109,15 +110,15 @@ public class DecisionQueue implements Serializable {
     }
 
     /**
-     * Attempts to complete a Decision by calling {@link Decision#decide(Outcome)}. This will cause the Step immediately following the
+     * Attempts to complete a Decision by calling {@link Decision#decide(Outcome, Context)}. This will cause the Step immediately following the
      * Decision (if any) to start. If the decision completes successfully, this method also removes the completed decision from the queue.
      *
      * @param decision the Decision for which the Outcome will be supplied
      * @param outcome the Outcome of the Decision
      * @throws WikiException if the succeeding Step cannot start for any reason
      */
-    public void decide( final Decision decision, final Outcome outcome ) throws WikiException {
-        decision.decide( outcome );
+    public void decide( final Decision decision, final Outcome outcome, final Context context ) throws WikiException {
+        decision.decide( outcome, context );
         if ( decision.isCompleted() ) {
             remove( decision );
         }
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 b1444f4..433955a 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
@@ -20,6 +20,7 @@ package org.apache.wiki.workflow;
 
 import org.apache.commons.lang3.time.StopWatch;
 import org.apache.log4j.Logger;
+import org.apache.wiki.api.core.Context;
 import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.api.core.Session;
 import org.apache.wiki.api.exceptions.WikiException;
@@ -29,21 +30,9 @@ import org.apache.wiki.event.WikiEvent;
 import org.apache.wiki.event.WikiEventEmitter;
 import org.apache.wiki.event.WorkflowEvent;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
+import java.io.*;
 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.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -282,7 +271,7 @@ public class DefaultWorkflowManager implements WorkflowManager {
                 // Add to DecisionQueue
                 case WorkflowEvent.DQ_ADDITION : addToDecisionQueue( decision ); break;
                 // Remove from DecisionQueue
-                case WorkflowEvent.DQ_REMOVAL  : removeFromDecisionQueue( decision ); break;
+                case WorkflowEvent.DQ_REMOVAL  : removeFromDecisionQueue( decision, event.getArg( 0, Context.class ) ); break;
                 default: break;
                 }
             }
@@ -313,7 +302,7 @@ public class DefaultWorkflowManager implements WorkflowManager {
         }
     }
 
-    protected void removeFromDecisionQueue( final Decision decision ) {
+    protected void removeFromDecisionQueue( final Decision decision, final Context context ) {
         // 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();
@@ -323,7 +312,7 @@ public class DefaultWorkflowManager implements WorkflowManager {
                 getDecisionQueue().remove( decision );
                 // Restart workflow
                 try {
-                    w.restart();
+                    w.restart( context );
                 } catch( final WikiException e ) {
                     LOG.error( "restarting workflow #" + w.getId() + " caused " + e.getMessage(), e );
                 }
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 fbae727..29cb4b2 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,6 +18,7 @@
  */
 package org.apache.wiki.workflow;
 
+import org.apache.wiki.api.core.Context;
 import org.apache.wiki.api.exceptions.WikiException;
 
 import java.io.Serializable;
@@ -49,12 +50,12 @@ public final class SimpleNotification extends Decision {
     }
     
     /**
-     * Convenience method that simply calls {@link #decide(Outcome)} with the value {@link Outcome#DECISION_ACKNOWLEDGE}.
+     * Convenience method that simply calls {@link #decide(Outcome, Context)} with the value {@link Outcome#DECISION_ACKNOWLEDGE}.
      *
      * @throws WikiException never
      */
-    public void acknowledge() throws WikiException {
-        this.decide( Outcome.DECISION_ACKNOWLEDGE  );
+    public void acknowledge( final Context context ) throws WikiException {
+        this.decide( Outcome.DECISION_ACKNOWLEDGE, context  );
     }
 
     /**
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 c73bcef..8f00810 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
@@ -18,6 +18,7 @@
  */
 package org.apache.wiki.workflow;
 
+import org.apache.wiki.api.core.Context;
 import org.apache.wiki.api.exceptions.WikiException;
 
 import java.io.Serializable;
@@ -96,11 +97,12 @@ public interface Step extends Serializable {
      * Note that successful execution of this methods does not necessarily mean that the Step is considered "complete"; rather, it just
      * means that it has executed. Therefore, it is possible that <code>execute</code> could run multiple times.
      * </p>
-     * 
+     *
+     * @param ctx executing wiki context.
      * @return the result of the Step, expressed as an Outcome
      * @throws WikiException if the step encounters errors while executing
      */
-    Outcome execute() throws WikiException;
+    Outcome execute( Context ctx ) throws WikiException;
 
     /**
      * The Principal responsible for completing this Step, such as a system user or actor assigned to a Decision.
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 10386c6..5e7abe9 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
@@ -18,18 +18,14 @@
  */
 package org.apache.wiki.workflow;
 
+import org.apache.wiki.api.core.Context;
 import org.apache.wiki.api.exceptions.WikiException;
 import org.apache.wiki.event.WikiEventEmitter;
 import org.apache.wiki.event.WorkflowEvent;
 
 import java.io.Serializable;
 import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -43,13 +39,13 @@ import java.util.concurrent.atomic.AtomicInteger;
  * A Workflow's state (obtained by {@link #getCurrentState()}) will be one of the following:
  * </p>
  * <ul>
- * <li><strong>{@link #CREATED}</strong>: after the Workflow has been instantiated, but before it has been started using the {@link #start()}
+ * <li><strong>{@link #CREATED}</strong>: after the Workflow has been instantiated, but before it has been started using the {@link #start(Context)}
  * method.</li>
- * <li><strong>{@link #RUNNING}</strong>: after the Workflow has been started using the {@link #start()} method, but before it has
+ * <li><strong>{@link #RUNNING}</strong>: after the Workflow has been started using the {@link #start(Context)} method, but before it has
  * finished processing all Steps. Note that a Workflow can only be started once; attempting to start it again results in an
  * IllegalStateException. Callers can place the Workflow into the WAITING state by calling {@link #waitstate()}.</li>
  * <li><strong>{@link #WAITING}</strong>: when the Workflow has temporarily paused, for example because of a pending Decision. Once the
- * responsible actor decides what to do, the caller can change the Workflow back to the RUNNING state by calling the {@link #restart()}
+ * responsible actor decides what to do, the caller can change the Workflow back to the RUNNING state by calling the {@link #restart(Context)}
  * method (this is done automatically by the Decision class, for instance, when the {@link Decision#decide(Outcome)} method is invoked)</li>
  * <li><strong>{@link #COMPLETED}</strong>: after the Workflow has finished processing all Steps, without errors.</li>
  * <li><strong>{@link #ABORTED}</strong>: if a Step has elected to abort the Workflow.</li>
@@ -60,7 +56,7 @@ import java.util.concurrent.atomic.AtomicInteger;
  * do not. See the {@link Step} class for more details.
  * </p>
  * <p>
- * After instantiating a new Workflow (but before telling it to {@link #start()}), calling classes should specify the first Step by
+ * After instantiating a new Workflow (but before telling it to {@link #start(Context)}), calling classes should specify the first Step by
  * executing the {@link #setFirstStep(Step)} method. Additional Steps can be chained by invoking the first step's
  * {@link Step#addSuccessor(Outcome, Step)} method.
  * </p>
@@ -70,7 +66,7 @@ import java.util.concurrent.atomic.AtomicInteger;
  * </p>
  * <ul>
  * <li>The Step's {@link Step#start()} method executes, which sets the start time.</li>
- * <li>The Step's {@link Step#execute()} method is called to begin processing, which will return an Outcome to indicate completion,
+ * <li>The Step's {@link Step#execute(Context)} method is called to begin processing, which will return an Outcome to indicate completion,
  * continuation or errors:</li>
  * <ul>
  * <li>{@link Outcome#STEP_COMPLETE} indicates that the execution method ran without errors, and that the Step should be considered
@@ -79,7 +75,7 @@ import java.util.concurrent.atomic.AtomicInteger;
  * be put into the WAITING state.</li>
  * <li>{@link Outcome#STEP_ABORT} indicates that the execution method encountered errors, and should abort the Step <em>and</em> the
  * Workflow as a whole. When this happens, the Workflow will set the current Step's Outcome to {@link Outcome#STEP_ABORT} and invoke the
- * Workflow's {@link #abort()} method. The Step's processing errors, if any, can be retrieved by {@link Step#getErrors()}.</li>
+ * Workflow's {@link #abort(Context)} method. The Step's processing errors, if any, can be retrieved by {@link Step#getErrors()}.</li>
  * </ul>
  * <li>The Outcome of the <code>execute</code> method also affects what happens next. Depending on the result (and assuming the Step did
  * not abort), the Workflow will either move on to the next Step or put the Workflow into the {@link Workflow#WAITING} state:</li>
@@ -212,7 +208,7 @@ public class Workflow implements Serializable {
     /**
      * Constructs a new Workflow object with a supplied message key, owner Principal, and undefined unique identifier {@link #ID_NOT_SET}.
      * Once instantiated the Workflow is considered to be in the {@link #CREATED} state; a caller must explicitly invoke the
-     * {@link #start()} method to begin processing.
+     * {@link #start(Context)} method to begin processing.
      *
      * @param messageKey the message key used to construct a localized workflow name, such as <code>workflow.saveWikiPage</code>
      * @param owner the Principal who owns the Workflow. Typically, this is the user who created and submitted it
@@ -237,7 +233,7 @@ public class Workflow implements Serializable {
      * to completion, but it cannot be called twice. It finishes by calling the {@link #cleanup()} method to flush retained objects.
      * If the Workflow had been previously aborted, this method throws an IllegalStateException.
      */
-    public final synchronized void abort() {
+    public final synchronized void abort( final Context context ) {
         // Check corner cases: previous abort or completion
         if( m_state == ABORTED ) {
             throw new IllegalStateException( "The workflow has already been aborted." );
@@ -248,7 +244,7 @@ public class Workflow implements Serializable {
 
         if( m_currentStep != null ) {
             if( m_currentStep instanceof Decision ) {
-                WikiEventEmitter.fireWorkflowEvent( m_currentStep, WorkflowEvent.DQ_REMOVAL );
+                WikiEventEmitter.fireWorkflowEvent( m_currentStep, WorkflowEvent.DQ_REMOVAL, context );
             }
             m_currentStep.setOutcome( Outcome.STEP_ABORT );
             m_history.addLast( m_currentStep );
@@ -444,7 +440,7 @@ public class Workflow implements Serializable {
     }
 
     /**
-     * Determines whether this Workflow has started; that is, its {@link #start()} method has been executed.
+     * Determines whether this Workflow has started; that is, its {@link #start(Context)} method has been executed.
      *
      * @return <code>true</code> if the workflow has been started; <code>false</code> if not.
      */
@@ -469,9 +465,10 @@ public class Workflow implements Serializable {
      * previously been paused, this method throws an IllegalStateException. If any of the Steps in this Workflow throw a WikiException,
      * the Workflow will abort and propagate the exception to callers.
      *
-     * @throws WikiException if the current task's {@link Task#execute()} method throws an exception
+     * @param context current wiki context
+     * @throws WikiException if the current task's {@link Task#execute( Context )} method throws an exception
      */
-    public final synchronized void restart() throws WikiException {
+    public final synchronized void restart( final Context context ) throws WikiException {
         if( m_state != WAITING ) {
             throw new IllegalStateException( "Workflow is not paused; cannot restart." );
         }
@@ -481,9 +478,9 @@ public class Workflow implements Serializable {
 
         // Process current step
         try {
-            processCurrentStep();
+            processCurrentStep( context );
         } catch( final WikiException e ) {
-            abort();
+            abort( context );
             throw e;
         }
     }
@@ -502,9 +499,9 @@ public class Workflow implements Serializable {
 
     /**
      * Sets the first Step for this Workflow, which will be executed immediately
-     * after the {@link #start()} method executes. Note than the Step is not
+     * after the {@link #start( Context )} method executes. Note than the Step is not
      * marked as the "current" step or added to the Workflow history until the
-     * {@link #start()} method is called.
+     * {@link #start( Context )} method is called.
      *
      * @param step the first step for the workflow
      */
@@ -528,9 +525,10 @@ public class Workflow implements Serializable {
      * method returns an {@linkplain IllegalStateException}. If any of the Steps in this Workflow throw a WikiException, the Workflow will
      * abort and propagate the exception to callers.
      *
+     * @param context current wiki context.
      * @throws WikiException if the current Step's {@link Step#start()} method throws an exception of any kind
      */
-    public final synchronized void start() throws WikiException {
+    public final synchronized void start( final Context context ) throws WikiException {
         if( m_state == ABORTED ) {
             throw new IllegalStateException( "Workflow cannot be started; it has already been aborted." );
         }
@@ -548,16 +546,16 @@ public class Workflow implements Serializable {
 
         // Process current step
         try {
-            processCurrentStep();
+            processCurrentStep( context );
         } catch( final WikiException e ) {
-            abort();
+            abort( context );
             throw e;
         }
     }
 
     /**
      * Sets the Workflow in the {@link #WAITING} state. If the Workflow is not running or has already been paused, this method throws an
-     * IllegalStateException. Once paused, the Workflow can be un-paused by executing the {@link #restart()} method.
+     * IllegalStateException. Once paused, the Workflow can be un-paused by executing the {@link #restart(Context)} method.
      */
     public final synchronized void waitstate() {
         if ( m_state != RUNNING ) {
@@ -599,20 +597,20 @@ public class Workflow implements Serializable {
     }
 
     /**
-     * Protected method that processes the current Step by calling {@link Step#execute()}. If the <code>execute</code> throws an
+     * Protected method that processes the current Step by calling {@link Step#execute( Context )}. If the <code>execute</code> throws an
      * exception, this method will propagate the exception immediately to callers without aborting.
      *
      * @throws WikiException if the current Step's {@link Step#start()} method throws an exception of any kind
      */
-    protected final void processCurrentStep() throws WikiException {
+    protected final void processCurrentStep( final Context context ) throws WikiException {
         while ( m_currentStep != null ) {
             // Start and execute the current step
             if( !m_currentStep.isStarted() ) {
                 m_currentStep.start();
             }
-            final Outcome result = m_currentStep.execute();
+            final Outcome result = m_currentStep.execute( context );
             if( Outcome.STEP_ABORT.equals( result ) ) {
-                abort();
+                abort( context );
                 break;
             }
 
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 aecf7de..6fe4c40 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
@@ -21,8 +21,10 @@ package org.apache.wiki.auth;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.wiki.TestEngine;
 import org.apache.wiki.WikiSessionTest;
+import org.apache.wiki.api.core.Context;
 import org.apache.wiki.api.core.Page;
 import org.apache.wiki.api.core.Session;
+import org.apache.wiki.api.spi.Wiki;
 import org.apache.wiki.auth.authorize.Group;
 import org.apache.wiki.auth.authorize.GroupManager;
 import org.apache.wiki.auth.permissions.PermissionFactory;
@@ -107,7 +109,8 @@ public class UserManagerTest {
         final int oldPageCount = pageManager.getTotalPageCount();
 
         // Setup Step 1: create a new user with random name
-        final Session session = m_engine.guestSession();
+        final Context context = Wiki.context().create( m_engine, m_engine.newHttpRequest(), "" );
+        final Session session = context.getWikiSession();
         final long now = System.currentTimeMillis();
         final String oldLogin = "TestLogin" + now;
         final String oldName = "Test User " + now;
@@ -118,7 +121,7 @@ public class UserManagerTest {
         profile.setLoginName( oldLogin );
         profile.setFullname( oldName );
         profile.setPassword( "password" );
-        m_mgr.setUserProfile( session, profile );
+        m_mgr.setUserProfile( context, profile );
 
         // 1a. Make sure the profile saved successfully and that we're logged in
         profile = m_mgr.getUserProfile( session );
@@ -160,7 +163,7 @@ public class UserManagerTest {
         profile.setLoginName( oldLogin );
         profile.setFullname( newName );
         profile.setPassword( "password" );
-        m_mgr.setUserProfile( session, profile );
+        m_mgr.setUserProfile( context, profile );
 
         // Test 1: the wiki session should have the new wiki name in Subject
         Principal[] principals = session.getPrincipals();
@@ -219,7 +222,7 @@ public class UserManagerTest {
         profile.setLoginName( newLogin );
         profile.setFullname( oldName );
         profile.setPassword( "password" );
-        m_mgr.setUserProfile( session, profile );
+        m_mgr.setUserProfile( context, profile );
 
         // Test 5: the wiki session should have the new login name in Subject
         principals = session.getPrincipals();
@@ -269,17 +272,17 @@ public class UserManagerTest {
         final int oldUserCount = m_db.getWikiNames().length;
 
         // Create a new user with random name
-        final Session session = m_engine.guestSession();
+        final Context context = Wiki.context().create( m_engine, m_engine.newHttpRequest(), "" );
         final String loginName = "TestUser" + String.valueOf( System.currentTimeMillis() );
         UserProfile profile = m_db.newProfile();
         profile.setEmail( "jspwiki.tests@mailinator.com" );
         profile.setLoginName( loginName );
         profile.setFullname( "FullName" + loginName );
         profile.setPassword( "password" );
-        m_mgr.setUserProfile( session, profile );
+        m_mgr.setUserProfile( context, profile );
 
         // Make sure the profile saved successfully
-        profile = m_mgr.getUserProfile( session );
+        profile = m_mgr.getUserProfile( context.getWikiSession() );
         Assertions.assertEquals( loginName, profile.getLoginName() );
         Assertions.assertEquals( oldUserCount + 1, m_db.getWikiNames().length );
 
@@ -296,7 +299,7 @@ public class UserManagerTest {
         final int oldUserCount = m_db.getWikiNames().length;
 
         // Create a new user with random name
-        final Session session = m_engine.guestSession();
+        final Context context = Wiki.context().create( m_engine, m_engine.newHttpRequest(), "" );
         final String loginName = "TestUser" + String.valueOf( System.currentTimeMillis() );
         final UserProfile profile = m_db.newProfile();
         profile.setEmail( "jspwiki.tests@mailinator.com" );
@@ -306,7 +309,7 @@ public class UserManagerTest {
 
         // Because user profile saves require approvals, we will catch a Redirect
         try {
-            m_mgr.setUserProfile( session, profile );
+            m_mgr.setUserProfile( context, profile );
             Assertions.fail( "We should have caught a DecisionRequiredException caused by approval!" );
         } catch( final DecisionRequiredException e ) {
         }
@@ -324,12 +327,12 @@ public class UserManagerTest {
         final List< Fact > facts = d.getFacts();
         Assertions.assertEquals( new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_FULL_NAME, profile.getFullname() ), facts.get( 0 ) );
         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_SUBMITTER, context.getWikiSession().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.getWorkflowContext().get( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE ) );
 
         // Approve the profile
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, context );
 
         // Make sure the profile saved successfully
         Assertions.assertEquals( oldUserCount + 1, m_db.getWikiNames().length );
@@ -347,7 +350,7 @@ public class UserManagerTest {
         final int oldUserCount = m_db.getWikiNames().length;
 
         // Create a new user with random name
-        final Session session = m_engine.guestSession();
+        final Context context = Wiki.context().create( m_engine, m_engine.newHttpRequest(), "" );
         final String loginName = "TestUser" + String.valueOf( System.currentTimeMillis() );
         final UserProfile profile = m_db.newProfile();
         profile.setEmail( "jspwiki.tests@mailinator.com" );
@@ -357,7 +360,7 @@ public class UserManagerTest {
 
         // Because user profile saves require approvals, we will catch a Redirect
         try {
-            m_mgr.setUserProfile( session, profile );
+            m_mgr.setUserProfile( context, profile );
             Assertions.fail( "We should have caught a DecisionRequiredException caused by approval!" );
         } catch( final DecisionRequiredException e ) {
         }
@@ -375,12 +378,12 @@ public class UserManagerTest {
         final List< Fact > facts = d.getFacts();
         Assertions.assertEquals( new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_FULL_NAME, profile.getFullname() ), facts.get( 0 ) );
         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_SUBMITTER, context.getWikiSession().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.getWorkflowContext().get( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE ) );
 
         // Approve the profile
-        d.decide( Outcome.DECISION_DENY );
+        d.decide( Outcome.DECISION_DENY, context );
 
         // Make sure the profile did NOT save
         Assertions.assertEquals( oldUserCount, m_db.getWikiNames().length );
@@ -392,7 +395,7 @@ public class UserManagerTest {
         final int oldUserCount = m_db.getWikiNames().length;
 
         // Create a new user with random name
-        final Session session = m_engine.guestSession();
+        final Context context = Wiki.context().create( m_engine, m_engine.newHttpRequest(), "" );
         final String loginName = "TestUser" + String.valueOf( System.currentTimeMillis() );
         final UserProfile profile = m_db.newProfile();
         profile.setEmail( "jspwiki.tests@mailinator.com" );
@@ -403,7 +406,7 @@ public class UserManagerTest {
         // Set the login name to collide with Janne's: should prohibit saving
         profile.setLoginName( "janne" );
         try {
-            m_mgr.setUserProfile( session, profile );
+            m_mgr.setUserProfile( context, profile );
             Assertions.fail( "UserManager allowed saving of user with login name 'janne', but it shouldn't have." );
         } catch( final DuplicateUserException e ) {
             // Good! That's what we expected; reset for next test
@@ -413,7 +416,7 @@ public class UserManagerTest {
         // Set the login name to collide with Janne's: should prohibit saving
         profile.setFullname( "Janne Jalkanen" );
         try {
-            m_mgr.setUserProfile( session, profile );
+            m_mgr.setUserProfile( context, profile );
             Assertions.fail( "UserManager allowed saving of user with login name 'janne', but it shouldn't have." );
         } catch( final DuplicateUserException e ) {
             // Good! That's what we expected
diff --git a/jspwiki-main/src/test/java/org/apache/wiki/auth/authorize/GroupManagerTest.java b/jspwiki-main/src/test/java/org/apache/wiki/auth/authorize/GroupManagerTest.java
index 25d9dc0..fd3fb33 100644
--- a/jspwiki-main/src/test/java/org/apache/wiki/auth/authorize/GroupManagerTest.java
+++ b/jspwiki-main/src/test/java/org/apache/wiki/auth/authorize/GroupManagerTest.java
@@ -54,7 +54,7 @@ public class GroupManagerTest
         final Properties props = TestEngine.getTestProperties();
 
         m_engine = new TestEngine( props );
-        m_groupMgr = m_engine.getGroupManager();
+        m_groupMgr = m_engine.getManager( GroupManager.class );
         m_session = WikiSessionTest.adminSession( m_engine );
 
         // Flush any pre-existing groups (left over from previous Assertions.failures, perhaps)
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 6dbb9ee..2960100 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
@@ -23,6 +23,7 @@ import org.apache.wiki.api.core.Context;
 import org.apache.wiki.api.exceptions.FilterException;
 import org.apache.wiki.api.exceptions.WikiException;
 import org.apache.wiki.api.filters.BasePageFilter;
+import org.apache.wiki.api.spi.Wiki;
 import org.apache.wiki.auth.Users;
 import org.apache.wiki.auth.WikiPrincipal;
 import org.apache.wiki.filters.FilterManager;
@@ -75,7 +76,7 @@ public class ApprovalWorkflowTest {
         Assertions.assertNull( w.getAttribute( "task.saveWikiPage") );
 
         // Start the workflow
-        w.start();
+        w.start( null );
 
         // Presave complete attribute should be set now, and current step should be Decision
         final Step decision = w.getCurrentStep();
@@ -108,7 +109,7 @@ public class ApprovalWorkflowTest {
         Assertions.assertTrue( notification instanceof SimpleNotification );
 
         // Now, approve the Decision and everything should complete
-        ((Decision)decision).decide( Outcome.DECISION_APPROVE );
+        ((Decision)decision).decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertTrue( w.isCompleted() );
         Assertions.assertNull( w.getCurrentStep() );
         Assertions.assertEquals( 3, w.getHistory().size() );
@@ -135,13 +136,13 @@ public class ApprovalWorkflowTest {
                                                             completionTask, rejectedMessageKey );
 
         // Start the workflow
-        w.start();
+        w.start( null );
 
         // Now, deny the Decision and the submitter should see a notification
         Step step = w.getCurrentStep();
         Assertions.assertTrue( step instanceof Decision );
         final Decision decision = (Decision)step;
-        decision.decide( Outcome.DECISION_DENY );
+        decision.decide( Outcome.DECISION_DENY, null );
         Assertions.assertFalse( w.isCompleted() );
 
         // Check that the notification is ok, then acknowledge it
@@ -149,7 +150,7 @@ public class ApprovalWorkflowTest {
         Assertions.assertTrue( step instanceof SimpleNotification );
         Assertions.assertEquals( rejectedMessageKey, step.getMessageKey() );
         final SimpleNotification notification = (SimpleNotification)step;
-        notification.acknowledge();
+        notification.acknowledge( null );
 
         // Workflow should be complete now
         Assertions.assertTrue( w.isCompleted() );
@@ -175,8 +176,9 @@ public class ApprovalWorkflowTest {
         Assertions.assertEquals( 1, decisions.size() );
 
         // Now, approve the decision and it should go away, and page should appear.
+        final Context context = Wiki.context().create( m_engine, Wiki.contents().page( m_engine, pageName ) );
         final Decision decision = decisions.iterator().next();
-        decision.decide( Outcome.DECISION_APPROVE );
+        decision.decide( Outcome.DECISION_APPROVE, context );
         Assertions.assertTrue( m_engine.getManager( PageManager.class ).wikiPageExists( pageName ) );
         decisions = m_dq.getActorDecisions( m_engine.adminSession() );
         Assertions.assertEquals( 0, decisions.size() );
@@ -205,7 +207,7 @@ public class ApprovalWorkflowTest {
 
         // Now, DENY the decision and the page should still not exist...
         Decision decision = decisions.iterator().next();
-        decision.decide( Outcome.DECISION_DENY );
+        decision.decide( Outcome.DECISION_DENY, null );
         Assertions.assertFalse( m_engine.getManager( PageManager.class ).wikiPageExists( pageName ) );
 
         // ...but there should also be a notification decision in Janne's queue
@@ -215,7 +217,7 @@ public class ApprovalWorkflowTest {
         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, null );
         decisions = m_dq.getActorDecisions( m_engine.janneSession() );
         Assertions.assertEquals( 0, decisions.size() );
     }
@@ -245,7 +247,7 @@ public class ApprovalWorkflowTest {
         }
 
         @Override
-        public Outcome execute() {
+        public Outcome execute( final Context context ) {
             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 fdcf1f9..cb17ab6 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
@@ -99,14 +99,14 @@ public class DecisionQueueTest {
         Assertions.assertEquals(3, m_queue.decisions().length);
 
         // Execute the competion for decision 1 (approve/deny)
-        m_queue.decide(d1, Outcome.DECISION_APPROVE);
+        m_queue.decide(d1, Outcome.DECISION_APPROVE, null);
 
         // Decision should be marked completed, and removed from queue
         Assertions.assertTrue(d1.isCompleted());
         Assertions.assertEquals(2, m_queue.decisions().length);
 
         // Execute the competion for decision 2 (approve/deny/hold)
-        m_queue.decide(d2, Outcome.DECISION_DENY);
+        m_queue.decide(d2, Outcome.DECISION_DENY, null);
 
         // Decision should be marked completed, and removed from queue
         Assertions.assertTrue(d2.isCompleted());
@@ -168,17 +168,17 @@ public class DecisionQueueTest {
         w.setFirstStep( startTask );
 
         // Start the workflow, and verify that the Decision is the current Step
-        w.start();
+        w.start( null );
         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();
+        final Decision d = 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 );
+        m_queue.decide( decision, Outcome.DECISION_APPROVE, null );
         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 6cd4e86..4b56d13 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
@@ -121,7 +121,7 @@ public class SimpleDecisionTest {
     public void testGetEndTime() throws WikiException {
         Assertions.assertEquals( Step.TIME_NOT_SET, m_decision.getEndTime() );
         m_decision.start();
-        m_decision.decide( Outcome.DECISION_APPROVE );
+        m_decision.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertTrue( ( Step.TIME_NOT_SET != m_decision.getEndTime() ) );
     }
 
@@ -134,7 +134,7 @@ public class SimpleDecisionTest {
     public void testGetOutcome() throws WikiException {
         Assertions.assertEquals( Outcome.STEP_CONTINUE, m_decision.getOutcome() );
         m_decision.start();
-        m_decision.decide( Outcome.DECISION_APPROVE );
+        m_decision.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertEquals( Outcome.DECISION_APPROVE, m_decision.getOutcome() );
     }
 
@@ -142,7 +142,7 @@ public class SimpleDecisionTest {
     public void testGetStartTime() throws WikiException {
         Assertions.assertEquals( Step.TIME_NOT_SET, m_decision.getStartTime() );
         m_decision.start();
-        m_decision.decide( Outcome.DECISION_APPROVE );
+        m_decision.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertTrue( ( Step.TIME_NOT_SET != m_decision.getStartTime() ) );
     }
 
@@ -155,7 +155,7 @@ public class SimpleDecisionTest {
     public void testIsCompleted() throws WikiException {
         Assertions.assertFalse( m_decision.isCompleted() );
         m_decision.start();
-        m_decision.decide( Outcome.DECISION_APPROVE );
+        m_decision.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertTrue( m_decision.isCompleted() );
     }
 
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 6cead93..dcbe890 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
@@ -19,6 +19,7 @@
 package org.apache.wiki.workflow;
 
 import org.apache.wiki.TestEngine;
+import org.apache.wiki.api.core.Context;
 import org.apache.wiki.api.exceptions.WikiException;
 import org.apache.wiki.auth.WikiPrincipal;
 import org.junit.jupiter.api.Assertions;
@@ -46,7 +47,7 @@ public class TaskTest {
             super( workflow.getId(), workflow.getAttributes(), "task.normal" );
         }
 
-        public Outcome execute() throws WikiException {
+        public Outcome execute( final Context context ) {
             return Outcome.STEP_COMPLETE;
         }
 
@@ -61,7 +62,7 @@ public class TaskTest {
             super( workflow.getId(), workflow.getAttributes(), "task.error" );
         }
 
-        public Outcome execute() throws WikiException {
+        public Outcome execute( final Context context ) {
             addError( "Found an error." );
             addError( "Found a second one!" );
             return Outcome.STEP_ABORT;
@@ -131,7 +132,7 @@ public class TaskTest {
     public void testGetEndTime() throws WikiException {
         Assertions.assertEquals( Step.TIME_NOT_SET, m_task.getEndTime() );
         m_task.start();
-        m_task.setOutcome( m_task.execute() );
+        m_task.setOutcome( m_task.execute( null ) );
         Assertions.assertTrue( ( Step.TIME_NOT_SET != m_task.getEndTime() ) );
     }
 
@@ -144,14 +145,14 @@ public class TaskTest {
     public void testGetOutcome() throws WikiException {
         Assertions.assertEquals( Outcome.STEP_CONTINUE, m_task.getOutcome() );
         m_task.start();
-        m_task.setOutcome( m_task.execute() );
+        m_task.setOutcome( m_task.execute( null ) );
         Assertions.assertEquals( Outcome.STEP_COMPLETE, m_task.getOutcome() );
 
         // Test the "error task"
         m_task = new ErrorTask( m_workflow );
         Assertions.assertEquals( Outcome.STEP_CONTINUE, m_task.getOutcome() );
         m_task.start();
-        m_task.setOutcome( m_task.execute() );
+        m_task.setOutcome( m_task.execute( null ) );
         Assertions.assertEquals( Outcome.STEP_ABORT, m_task.getOutcome() );
     }
 
@@ -159,7 +160,7 @@ public class TaskTest {
     public void testGetStartTime() throws WikiException {
         Assertions.assertEquals( Step.TIME_NOT_SET, m_task.getStartTime() );
         m_task.start();
-        m_task.execute();
+        m_task.execute( null );
         Assertions.assertTrue( ( Step.TIME_NOT_SET != m_task.getStartTime() ) );
     }
 
@@ -172,7 +173,7 @@ public class TaskTest {
     public void testIsCompleted() throws WikiException {
         Assertions.assertFalse( m_task.isCompleted() );
         m_task.start();
-        m_task.setOutcome( m_task.execute() );
+        m_task.setOutcome( m_task.execute( null ) );
         Assertions.assertTrue( m_task.isCompleted() );
     }
 
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 6735029..ec069b2 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
@@ -56,7 +56,7 @@ public class WorkflowManagerTest {
     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.assertFalse( w.isStarted() );
-        w.start();
+        w.start( null );
         Assertions.assertNotEquals( Workflow.ID_NOT_SET, w.getId() );
         Assertions.assertTrue( w.isStarted() );
     }
@@ -68,7 +68,7 @@ public class WorkflowManagerTest {
         Assertions.assertEquals( 0, wm.getCompletedWorkflows().size() );
 
         // After starting, there should be 1 in the cache
-        w.start();
+        w.start( null );
         Assertions.assertEquals( 1, wm.getWorkflows().size() );
         Assertions.assertEquals( 0, wm.getCompletedWorkflows().size() );
         final Workflow workflow = wm.getWorkflows().iterator().next();
@@ -76,7 +76,7 @@ public class WorkflowManagerTest {
 
         // After forcing a decision on step 2, the workflow should complete and vanish from the cache
         final Decision d = ( Decision )w.getCurrentStep();
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertEquals( 0, wm.getWorkflows().size() );
         Assertions.assertEquals( 1, wm.getCompletedWorkflows().size() );
     }
@@ -111,7 +111,7 @@ public class WorkflowManagerTest {
 
         final Workflow workflow = dwm.m_workflows.iterator().next();
         final Decision d = ( Decision )workflow.getCurrentStep();
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, null );
         dwm.actionPerformed( new WorkflowEvent( workflow, WorkflowEvent.COMPLETED ) );
         dwm.actionPerformed( new WorkflowEvent( d, WorkflowEvent.DQ_REMOVAL ) );
         Assertions.assertEquals( 0, dwm.getWorkflows().size() );
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 44739ce..ef19529 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
@@ -107,14 +107,14 @@ public class WorkflowTest {
         Assertions.assertEquals( "MyPage", args[ 2 ] );
 
         // After start (at Decision), arg1=Owner1, arg2=Admin, arg3=MyPage
-        w.start();
+        w.start( null );
         args = w.getMessageArguments();
         Assertions.assertEquals( "Owner1", args[ 0 ] );
         Assertions.assertEquals( "Admin", args[ 1 ] );
         Assertions.assertEquals( "MyPage", args[ 2 ] );
 
         // After end, arg1=Owner1, arg2=-, arg3=MyPage
-        decision.decide( Outcome.DECISION_APPROVE );
+        decision.decide( Outcome.DECISION_APPROVE, null );
         args = w.getMessageArguments();
         Assertions.assertEquals( "Owner1", args[ 0 ] );
         Assertions.assertEquals( "-", args[ 1 ] );
@@ -153,13 +153,13 @@ public class WorkflowTest {
     @Test
     public void testStart() throws WikiException {
         Assertions.assertFalse( w.isStarted() );
-        w.start();
+        w.start( null );
         Assertions.assertTrue( w.isStarted() );
     }
 
     @Test
     public void testWaitstate() throws WikiException {
-        w.start();
+        w.start( null );
 
         // Default workflow should have hit the Decision step and put itself
         // into WAITING
@@ -168,12 +168,12 @@ public class WorkflowTest {
 
     @Test
     public void testRestart() throws WikiException {
-        w.start();
+        w.start( null );
 
         // Default workflow should have hit the Decision step and put itself
         // into WAITING
         Assertions.assertEquals( Workflow.WAITING, w.getCurrentState() );
-        w.restart();
+        w.restart( null );
         Assertions.assertEquals( Workflow.WAITING, w.getCurrentState() );
     }
 
@@ -181,12 +181,12 @@ public class WorkflowTest {
     public void testAbortBeforeStart() throws WikiException {
         // Workflow hasn't been started yet
         Assertions.assertFalse( w.isAborted() );
-        w.abort();
+        w.abort( null );
         Assertions.assertTrue( w.isAborted() );
 
         // Try to start anyway
         try {
-            w.start();
+            w.start( null );
         } catch( final IllegalStateException e ) {
             // Swallow
             return;
@@ -199,13 +199,13 @@ public class WorkflowTest {
     public void testAbortDuringWait() throws WikiException {
         // Start workflow, then abort while in WAITING state
         Assertions.assertFalse( w.isAborted() );
-        w.start();
-        w.abort();
+        w.start( null );
+        w.abort( null );
         Assertions.assertTrue( w.isAborted() );
 
         // Try to restart anyway
         try {
-            w.restart();
+            w.restart( null );
         } catch( final IllegalStateException e ) {
             // Swallow
             return;
@@ -218,13 +218,13 @@ public class WorkflowTest {
     public void testAbortAfterCompletion() throws WikiException {
         // Start workflow, then abort after completion
         Assertions.assertFalse( w.isAborted() );
-        w.start();
+        w.start( null );
         final Decision d = ( Decision )w.getCurrentStep();
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, null );
 
         // Try to abort anyway
         try {
-            w.abort();
+            w.abort( null );
             Assertions.assertTrue( w.isAborted() );
         } catch( final IllegalStateException e ) {
             // Swallow
@@ -237,25 +237,24 @@ public class WorkflowTest {
     @Test
     public void testCurrentState() throws WikiException {
         Assertions.assertEquals( Workflow.CREATED, w.getCurrentState() );
-        w.start();
+        w.start( null );
         Assertions.assertEquals( Workflow.WAITING, w.getCurrentState() );
         final Decision d = ( Decision )w.getCurrentStep();
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertEquals( Workflow.COMPLETED, w.getCurrentState() );
     }
 
     @Test
     public void testCurrentStep() throws WikiException {
         Assertions.assertNull( w.getCurrentStep() );
-        w.start();
+        w.start( null );
 
         // Workflow stops at the decision step
         Assertions.assertEquals( decision, w.getCurrentStep() );
         final Decision d = ( Decision )w.getCurrentStep();
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, null );
 
-        // After we decide, it blows through step 3 and leaves us with a null
-        // step (done)
+        // After we decide, it blows through step 3 and leaves us with a null step (done)
         Assertions.assertNull( w.getCurrentStep() );
     }
 
@@ -267,16 +266,15 @@ public class WorkflowTest {
         Assertions.assertNull( w.previousStep( decision ) );
         Assertions.assertNull( w.previousStep( finishTask ) );
 
-        // Once we start, initTask and decisions' predecessors are known, but
-        // finish task is indeterminate
-        w.start();
+        // Once we start, initTask and decisions' predecessors are known, but finish task is indeterminate
+        w.start( null );
         Assertions.assertNull( w.previousStep( initTask ) );
         Assertions.assertEquals( initTask, w.previousStep( decision ) );
         Assertions.assertNull( w.previousStep( finishTask ) );
 
         // Once we decide, the finish task returns the correct predecessor
         final Decision d = ( Decision )w.getCurrentStep();
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertNull( w.previousStep( initTask ) );
         Assertions.assertEquals( initTask, w.previousStep( decision ) );
         Assertions.assertEquals( decision, w.previousStep( finishTask ) );
@@ -288,22 +286,22 @@ public class WorkflowTest {
         Assertions.assertNull( w.getCurrentActor() );
 
         // After starting, actor should be GroupPrincipal Admin
-        w.start();
+        w.start( null );
         Assertions.assertEquals( new GroupPrincipal( "Admin" ), w.getCurrentActor() );
 
         // After decision, actor should be null again
         final Decision d = ( Decision )w.getCurrentStep();
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertNull( w.getCurrentActor() );
     }
 
     @Test
     public void testHistory() throws WikiException {
         Assertions.assertEquals( 0, w.getHistory().size() );
-        w.start();
+        w.start( null );
         Assertions.assertEquals( 2, w.getHistory().size() );
         final Decision d = ( Decision )w.getCurrentStep();
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertEquals( 3, w.getHistory().size() );
     }
 
@@ -311,10 +309,10 @@ public class WorkflowTest {
     public void testGetStartTime() throws WikiException {
         // Start time should be not be set until we start the workflow
         Assertions.assertEquals( Step.TIME_NOT_SET, w.getStartTime() );
-        w.start();
+        w.start( null );
         Assertions.assertNotSame( Step.TIME_NOT_SET, w.getStartTime() );
         final Decision d = ( Decision )w.getCurrentStep();
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertNotSame( Step.TIME_NOT_SET, w.getStartTime() );
     }
 
@@ -322,10 +320,10 @@ public class WorkflowTest {
     public void testGetEndTime() throws WikiException {
         // End time should be not set until we finish all 3 steps
         Assertions.assertEquals( Step.TIME_NOT_SET, w.getEndTime() );
-        w.start();
+        w.start( null );
         Assertions.assertEquals( Step.TIME_NOT_SET, w.getEndTime() );
         final Decision d = ( Decision )w.getCurrentStep();
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertNotSame( Step.TIME_NOT_SET, w.getEndTime() );
     }
 
@@ -333,31 +331,24 @@ public class WorkflowTest {
     public void testIsCompleted() throws WikiException {
         // Workflow isn't completed until we finish all 3 steps
         Assertions.assertFalse( w.isCompleted() );
-        w.start();
+        w.start( null );
         Assertions.assertFalse( w.isCompleted() );
         final Decision d = ( Decision )w.getCurrentStep();
-        d.decide( Outcome.DECISION_APPROVE );
+        d.decide( Outcome.DECISION_APPROVE, null );
         Assertions.assertTrue( w.isCompleted() );
     }
 
     @Test
     public void testIsStarted() throws WikiException {
         Assertions.assertFalse( w.isStarted() );
-        w.start();
+        w.start( null );
         Assertions.assertTrue( w.isStarted() );
     }
 
     @Test
     public void testStartTwice() throws WikiException {
-        w.start();
-        try {
-            w.start();
-        } catch( final IllegalStateException e ) {
-            // Swallow
-            return;
-        }
-        // We should never get here
-        Assertions.fail( "Workflow allowed itself to be started twice!" );
+        w.start( null );
+        Assertions.assertThrows( IllegalStateException.class, () -> w.start( null ) );
     }
 
     @Test