You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by sk...@apache.org on 2008/07/11 10:21:32 UTC

svn commit: r675860 - in /myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow: FlowHandler.java FlowHandler.java.sk FlowInfo.java FlowNavigationHandler.java FlowNavigationHandler.java.sk FlowNavigator.java Navigator.java.sk

Author: skitching
Date: Fri Jul 11 01:21:30 2008
New Revision: 675860

URL: http://svn.apache.org/viewvc?rev=675860&view=rev
Log:
Clean up code in NavigationHandler (get rid of ugly Selection class).
Add comments.

Added:
    myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java.sk   (with props)
    myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java.sk   (with props)
    myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigator.java   (with props)
    myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/Navigator.java.sk   (with props)
Modified:
    myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java
    myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowInfo.java
    myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java

Modified: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java?rev=675860&r1=675859&r2=675860&view=diff
==============================================================================
--- myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java (original)
+++ myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java Fri Jul 11 01:21:30 2008
@@ -54,25 +54,6 @@
     private static final Log log = LogFactory.getLog(FlowHandler.class);
 
     /**
-     * A simple object that allows methods to return multiple values.
-     * <p>
-     * TODO: consider making this an "active" object rather than a
-     * passive one, ie for it to decide which view to return based
-     * on its internal state rather than just being a data structure.
-     * Maybe this entire class should be used as the "selection" object,
-     * ie a FlowHandler is a handler for a specific request and is either
-     * created by navhandler and passed to viewhandler, or created by
-     * viewhandler?
-     */
-    static class Selection
-    {
-        public String viewId;
-        public UIViewRoot viewRoot;
-        public String outcome;
-        public FlowInfo flowInfo; // null unless a flowcall is being started
-    }
-
-    /**
      * Return the FlowCall object associated with the current conversation
      * context (if any).
      * <p>
@@ -144,7 +125,7 @@
      * postback has caused a navigation to occur. Note that in that case we do
      * not yet know what viewId we are going <i>to</i>.
      */
-    static void processCall(Selection sel, FacesContext facesContext, String oldViewId, String outcome)
+    static void processCall(FlowNavigator nav, FacesContext facesContext, String oldViewId, String outcome)
     {
         log.debug("processCall: [" + String.valueOf(oldViewId) + "] outcome: " + outcome);
 
@@ -158,22 +139,24 @@
             if (outcome.equals(fa.getCancelWhen()))
             {
                 // cancel current flow
-                sel.viewId = flowInfo.getCallerViewId();
-                sel.viewRoot = flowInfo.getCallerViewRoot();
+                String callerViewId = flowInfo.getCallerViewId();
+                UIViewRoot callerViewRoot = flowInfo.getCallerViewRoot();
 
                 ConversationManager cm = ConversationManager.getInstance(true);
                 ConversationContext ctx = cm.getCurrentConversationContext();
                 ConversationContext parent = ctx.getParent();
                 cm.activateConversationContext(parent);
                 cm.removeAndInvalidateConversationContext(ctx);
+                
+                nav.goToView(callerViewRoot, callerViewId);
                 return;
             }
 
             if (outcome.equals(fa.getCommitWhen()))
             {
                 // commit current flow
-                sel.viewId = flowInfo.getCallerViewId();
-                sel.viewRoot = flowInfo.getCallerViewRoot();
+                String callerViewId = flowInfo.getCallerViewId();
+                UIViewRoot callerViewRoot = flowInfo.getCallerViewRoot();
 
                 ConversationManager cm = ConversationManager.getInstance(true);
                 ConversationContext ctx = cm.getCurrentConversationContext();
@@ -204,14 +187,12 @@
                     Object actionOutcome = onCommit.execute(facesContext);
                     if (actionOutcome != null)
                     {
-                        // Note: sel.viewId is already set; that is important as the
-                        // navigation handler will use that when choosing the nav-case.
-                        sel.outcome = actionOutcome.toString();
+                        nav.goToOutcome(callerViewRoot, callerViewId, actionOutcome.toString());
+                        return;
                     }
                 }
 
-                // Return with sel holding the restored view and the parent context
-                // selected as the active one.
+                nav.goToView(callerViewRoot, callerViewId);
                 return;
             }
         }
@@ -234,9 +215,6 @@
 
             child.setAttribute("flowInfo", flowInfo);
             cm.activateConversationContext(child);
-
-            sel.flowInfo = flowInfo;
-            return;
         }
     }
 
@@ -370,6 +348,13 @@
         }
     }
 
+    /**
+     * Return the FlowConfig object associated with the specified viewId, or null if
+     * none exists.
+     * <p>
+     * The current implementation just looks for a "-flow.xml" file relative to the
+     * specified view.
+     */
     private static FlowConfig getFlowConfig(String viewId)
     {
         log.debug("getflowConfig for [" + viewId + "]");
@@ -383,20 +368,18 @@
         }
 
         String path = viewId.substring(0, lastDot) + "-flow.xml";
-        log.debug("getFlowConfig: path=" + path);
         InputStream is = ec.getResourceAsStream(path);
         if (is == null)
         {
-            log.debug("getFlowConfig: not found");
+            log.debug("getFlowConfig: not found at " + path);
             return null;
         }
         try
         {
-            log.debug("getFlowConfig: parsing");
             InputSource source = new InputSource(is);
             source.setSystemId(path);
             FlowConfig cfg = FlowDigester.digest(source);
-            log.debug("getFlowConfig: parsed. " + cfg.toString());
+            log.debug("getFlowConfig: found at " + path);
             return cfg;
         }
         finally
@@ -412,6 +395,10 @@
         }
     }
 
+    /**
+     * Return the FlowInfo object stored in the current conversation
+     * context (if any).
+     */
     private static FlowInfo getFlowInfo()
     {
         ConversationManager cm = ConversationManager.getInstance(false);
@@ -429,11 +416,21 @@
         return getFlowInfo(context);
     }
 
+    /**
+     * Return the FlowInfo object associated with the specified conversation
+     * context (if any).
+     */
     private static FlowInfo getFlowInfo(ConversationContext context)
     {
         return (FlowInfo) context.getAttribute("flowInfo");
     }
 
+    /**
+     * Assuming that the specified viewId is the entry page for a flow, return a
+     * viewId prefix that will match all views in the same flow.
+     * <p>
+     * This implementation assumes that a flow always lives in its own directory.
+     */
     private static String getFlowPath(String viewId)
     {
         // just return everything up to (and including) the final slash

Added: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java.sk
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java.sk?rev=675860&view=auto
==============================================================================
--- myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java.sk (added)
+++ myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java.sk Fri Jul 11 01:21:30 2008
@@ -0,0 +1,536 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.orchestra.flow;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.myfaces.orchestra.conversation.ConversationContext;
+import org.apache.myfaces.orchestra.conversation.ConversationManager;
+import org.apache.myfaces.orchestra.flow.config.FlowAccept;
+import org.apache.myfaces.orchestra.flow.config.FlowCall;
+import org.apache.myfaces.orchestra.flow.config.FlowConfig;
+import org.apache.myfaces.orchestra.flow.config.FlowOnCommit;
+import org.apache.myfaces.orchestra.flow.config.FlowParamAccept;
+import org.apache.myfaces.orchestra.flow.config.FlowParamSend;
+import org.apache.myfaces.orchestra.flow.config.FlowReturnAccept;
+import org.apache.myfaces.orchestra.flow.config.FlowReturnSend;
+import org.apache.myfaces.orchestra.flow.digest.FlowDigester;
+import org.apache.myfaces.orchestra.lib.OrchestraException;
+import org.xml.sax.InputSource;
+
+/**
+ * Common logic for managing orchestra flows, called from both FlowNavigationHandler
+ * and FlowViewHandler.
+ */
+public class FlowHandler
+{
+    private static final Log log = LogFactory.getLog(FlowHandler.class);
+
+    /**
+     * A simple object that allows methods to return multiple values.
+     * <p>
+     * TODO: consider making this an "active" object rather than a
+     * passive one, ie for it to decide which view to return based
+     * on its internal state rather than just being a data structure.
+     * Maybe this entire class should be used as the "selection" object,
+     * ie a FlowHandler is a handler for a specific request and is either
+     * created by navhandler and passed to viewhandler, or created by
+     * viewhandler?
+     */
+    static class Selection
+    {
+        public String viewId;
+        public UIViewRoot viewRoot;
+        public String outcome;
+        public FlowInfo flowInfo; // null unless a flowcall is being started
+    }
+
+    /**
+     * Return the FlowCall object associated with the current conversation
+     * context (if any).
+     * <p>
+     * There will be one if the current context was created by some page
+     * doing a "call" to a flow.
+     * <p>
+     * Returns null if there is no FlowCall object in the current conversation
+     * context.
+     */
+    static FlowCall getFlowCall(String viewId, String outcome)
+    {
+        if (viewId == null)
+        {
+            return null;
+        }
+
+        FlowConfig config = getFlowConfig(viewId);
+        if (config == null)
+        {
+            log.debug("No flowcall for " + viewId);
+            return null;
+        }
+
+        FlowCall fc = config.getFlowCall(outcome);
+        log.debug("Flowcall for " + viewId + " outcome: " + outcome + " is " + String.valueOf(fc));
+        return fc;
+    }
+
+    /**
+     * Return the FlowAccept object associated with the current conversation
+     * context (if any).
+     * <p>
+     * There will be one if the current context was created by some page
+     * doing a "call" to a flow, AND the flow call initialisation has
+     * completed. 
+     */
+    static FlowAccept getFlowAccept(FlowInfo flowInfo, String viewId)
+    {
+        if ((flowInfo != null) && (flowInfo.getFlowAccept() != null))
+        {
+            // ok, we are already within a configured flow.
+            return flowInfo.getFlowAccept();
+        }
+
+        FlowConfig config = getFlowConfig(viewId);
+        if (config == null)
+        {
+            log.debug("No flowaccept for " + viewId);
+            return null;
+        }
+
+        FlowAccept fa = config.getFlowAccept();
+        log.debug("FlowAccept for " + viewId + " is " + String.valueOf(fa));
+        return fa;
+    }
+
+    /**
+     * Do actions that do not depend on properties of the called flow (if any).
+     * <p>
+     * Without knowing anything about the target of the navigation (other than
+     * its viewId), the following can still be done:
+     * <ul>
+     * <li>commit or cancel the current flow if the nav outcome matches</li>
+     * <li>determine whether this navigation triggers a new flow. If so, create
+     * a child context and place a partially-initialised FlowInfo object in it.</li>
+     * </ul> 
+     * <p>
+     * This is expected to be called from the FlowNavigationHandler after some
+     * postback has caused a navigation to occur. Note that in that case we do
+     * not yet know what viewId we are going <i>to</i>.
+     */
+    static void processCall(Navigator nav, FacesContext facesContext, String oldViewId, String outcome)
+    {
+        log.debug("processCall: [" + String.valueOf(oldViewId) + "] outcome: " + outcome);
+
+        // Handle COMMIT and CANCEL
+        FlowInfo flowInfo = getFlowInfo();
+        if (flowInfo != null)
+        {
+            // we are in a flow - are we leaving it?
+            FlowAccept fa = flowInfo.getFlowAccept();
+
+            if (outcome.equals(fa.getCancelWhen()))
+            {
+                // cancel current flow
+                String callerViewId = flowInfo.getCallerViewId();
+                UIViewRoot callerViewRoot = flowInfo.getCallerViewRoot();
+
+                ConversationManager cm = ConversationManager.getInstance(true);
+                ConversationContext ctx = cm.getCurrentConversationContext();
+                ConversationContext parent = ctx.getParent();
+                cm.activateConversationContext(parent);
+                cm.removeAndInvalidateConversationContext(ctx);
+                
+                nav.goToView(callerViewId, callerViewRoot);
+                return;
+            }
+
+            if (outcome.equals(fa.getCommitWhen()))
+            {
+                // commit current flow
+                String callerViewId = flowInfo.getCallerViewId();
+                UIViewRoot callerViewRoot = flowInfo.getCallerViewRoot();
+
+                ConversationManager cm = ConversationManager.getInstance(true);
+                ConversationContext ctx = cm.getCurrentConversationContext();
+                ConversationContext parent = ctx.getParent();
+
+                // With the child conversationContext currently active, read
+                // all the output parameters
+                Map map = flowInfo.getFlowAccept().readReturnParams(facesContext);
+
+                // Activate the parent and discard the child context
+                cm.activateConversationContext(parent);
+                cm.removeAndInvalidateConversationContext(ctx);
+
+                // Now with the original context active, write parameters back.
+                flowInfo.getFlowCall().writeAcceptParams(facesContext, map);
+
+                // And execute any actions the caller wants to run.
+                // Hmm..but these cannot do anything to set the viewroot, because
+                // we stomp over that after returning from this method. Maybe this
+                // method should be responsible for setting up viewroot itself, and
+                // return a boolean to say it did it?
+                //
+                // And should we restore the saved view before running these callbacks,
+                // in case the invoked code tries to access it?
+                FlowOnCommit onCommit = flowInfo.getFlowCall().getOnCommit();
+                if (onCommit != null)
+                {
+                    Object actionOutcome = onCommit.execute(facesContext);
+                    if (actionOutcome != null)
+                    {
+                        nav.goToOutcome(callerViewId, callerViewRoot, outcome);
+                        return;
+                    }
+                }
+
+                nav.goToView(callerViewId, callerViewRoot);
+                return;
+            }
+        }
+
+        // OK, we know we are not committing or canceling a flow. Are we trying to
+        // enter one?
+        FlowCall flowCall = getFlowCall(oldViewId, outcome);
+        if (flowCall != null)
+        {
+            // fetch parameters from caller into a map
+            Map data = flowCall.readSendParams(facesContext);
+
+            // Build a FlowInfo object for the called flow to use.
+            flowInfo = new FlowInfo(oldViewId, facesContext.getViewRoot(), flowCall, data);
+            
+            // create child context
+            ConversationManager cm = ConversationManager.getInstance(true);
+            ConversationContext parent = cm.getCurrentConversationContext();
+            ConversationContext child = cm.createConversationContext(parent);
+
+            child.setAttribute("flowInfo", flowInfo);
+            cm.activateConversationContext(child);
+        }
+
+        // do normal nav
+        nav.goToOutcome(outcome);
+    }
+
+    /**
+     * Handle case where the user is in a flow, then navigates somehow to a page that is not
+     * within the flow.
+     */
+    private static boolean isAbnormalFlowExit(FlowInfo flowInfo, String newViewId)
+    {
+        // Are we leaving a flow in an abnormal manner?
+        if (flowInfo != null)
+        {
+            String flowPath = getFlowPath(newViewId);
+            if (!flowPath.startsWith(flowInfo.getFlowPath()))
+            {
+                // User must have navigated away from the current flow. Walk up the tree invalidating
+                // each context until we find a flow that does match the new path, or we find the root
+                // context (which never has a FlowInfo in it).
+                ConversationManager cm = ConversationManager.getInstance(true);
+                ConversationContext ctx = cm.getCurrentConversationContext();
+                ConversationContext parent = ctx.getParent();
+                for(;;)
+                {
+                    if (parent == null)
+                    {
+                        // After discarding all invalid flows, let normal navigation occur
+                        // to the new viewId. There is no view to "restore".. 
+                        break;
+                    }
+    
+                    cm.activateConversationContext(parent);
+                    cm.removeAndInvalidateConversationContext(ctx);
+                    ctx = parent;
+                    parent = ctx.getParent();
+                    
+                    FlowInfo fi = getFlowInfo(ctx);
+                    if ((fi != null) && (flowPath.startsWith(fi.getFlowPath())))
+                    {
+                        // ok, the view the user wants to navigate to is within
+                        // the context just activated, so we now have things set
+                        // up as required; break out of loop.
+                        break;
+                    }
+                }
+
+                return true;
+            }
+        }
+
+        // nope, this is just a normal navigation
+        return false;
+    }
+
+    /**
+     * Handle case where we have just navigated to a new page, and the page that caused the
+     * navigation did a flow-call.
+     * <p>
+     * When this is true, a child context will already have been created, and a half-initialised
+     * FlowInfo object will be in the context. We need to now fetch the called flow's specs,
+     * check that the caller and called flows match in type and parameters, then finish
+     * initialising the FlowInfo and import the passed params into the child context.
+     * <p>
+     * This is expected to be called from FlowViewHandler when a new view is being created
+     * (due either to a GET or POST from the user, or an internal forward due to navigation.
+     */
+    static boolean isNewFlowEntry(FacesContext facesContext, FlowInfo flowInfo, String newViewId)
+    {
+        if (flowInfo == null)
+        {
+            // No, the processCall method has not created a new partially-initialised
+            // FlowInfo object.
+            return false;
+        }
+
+        if (flowInfo.getFlowAccept() != null)
+        {
+            // No, the processCall method has not created a new partially-initialised
+            // FlowInfo object.
+            return false;
+        }
+
+        FlowAccept flowAccept = getFlowAccept(flowInfo, newViewId);
+        if (flowAccept == null)
+        {
+            // TODO: discard all flow stacks here for safety?
+            log.debug("isNewFlowEntry: Error: invocation of flow without callee declaration");
+            throw new OrchestraException("Invocation of flow without callee declaration");
+        }
+
+        log.debug("isNewFlowEntry: new flow detected.");
+        // validate flowCall against flowAccept. TODO: discard flow stacks on error?
+        validateCall(flowInfo.getFlowCall(), flowAccept);
+
+        // finish initialising the new flowInfo
+        String flowPath = getFlowPath(newViewId);
+        flowInfo.setAcceptInfo(newViewId, flowPath, flowAccept);
+
+        // push parameters from caller to callee
+        Map data = flowInfo.getData();
+        flowAccept.writeAcceptParams(facesContext, data);
+
+        return true;
+    }
+
+    /**
+     * Invoked to handle actions that require info about the called page, but not the caller.
+     */
+    static void processAccept(FacesContext facesContext, String newViewId)
+    {
+        log.debug("processAccept: [" + newViewId + "]");
+        FlowInfo flowInfo = getFlowInfo();
+        
+        if (isNewFlowEntry(facesContext, flowInfo, newViewId))
+        {
+            return;
+        }
+
+        if (isAbnormalFlowExit(flowInfo, newViewId))
+        {
+            return;
+        }
+
+        FlowAccept flowAccept = getFlowAccept(flowInfo, newViewId);
+        boolean isFlowEntryPoint = (flowAccept != null);
+        boolean isInFlow = (flowInfo != null);
+
+        if (isFlowEntryPoint && !isInFlow)
+        {
+            log.debug("processAccept: Error: invocation of flow without caller declaration");
+            throw new OrchestraException("Invocation of flow without caller declaration");
+        }
+    }
+
+    private static FlowConfig getFlowConfig(String viewId)
+    {
+        log.debug("getflowConfig for [" + viewId + "]");
+        FacesContext fc = FacesContext.getCurrentInstance();
+        ExternalContext ec = fc.getExternalContext();
+
+        int lastDot = viewId.lastIndexOf(".");
+        if (lastDot == -1)
+        {
+            return null;
+        }
+
+        String path = viewId.substring(0, lastDot) + "-flow.xml";
+        log.debug("getFlowConfig: path=" + path);
+        InputStream is = ec.getResourceAsStream(path);
+        if (is == null)
+        {
+            log.debug("getFlowConfig: not found");
+            return null;
+        }
+        try
+        {
+            log.debug("getFlowConfig: parsing");
+            InputSource source = new InputSource(is);
+            source.setSystemId(path);
+            FlowConfig cfg = FlowDigester.digest(source);
+            log.debug("getFlowConfig: parsed. " + cfg.toString());
+            return cfg;
+        }
+        finally
+        {
+            try
+            {
+                is.close();
+            }
+            catch(Exception e)
+            {
+                // ignore
+            }
+        }
+    }
+
+    private static FlowInfo getFlowInfo()
+    {
+        ConversationManager cm = ConversationManager.getInstance(false);
+        if (cm == null)
+        {
+            return null;
+        }
+
+        ConversationContext context = cm.getCurrentConversationContext();
+        if (context == null)
+        {
+            return null;
+        }
+
+        return getFlowInfo(context);
+    }
+
+    private static FlowInfo getFlowInfo(ConversationContext context)
+    {
+        return (FlowInfo) context.getAttribute("flowInfo");
+    }
+
+    private static String getFlowPath(String viewId)
+    {
+        // just return everything up to (and including) the final slash
+        int idx = viewId.lastIndexOf('/');
+        if (idx <= 0)
+        {
+            // this cannot possibly be a flow
+            return null;
+        }
+        return viewId.substring(0, idx +1);
+    }
+
+    /**
+     * Ensure that the specified FlowCall object is compatible with this FlowAccept.
+     * <p>
+     * The parameters sent by the caller should match the params accepted by the called
+     * flow. On return, the parameters sent by the called flow should match the params
+     * accepted by the caller.
+     * <p>
+     * This method needs to be consistent with FlowAccept.writeAcceptParams method, but
+     * should report better error messages. Possibly the writeAcceptParams method's error
+     * messages could just be improved. However it is useful to be able to perform this
+     * check earlier than before actually processing the return result.
+     * <p>
+     * This method also needs to be consistent with FlowCall.writeAcceptParams method.
+     */
+    static private void validateCall(FlowCall flowCall, FlowAccept flowAccept)
+    {
+        // check type matches
+        if (!flowCall.getService().equals(flowAccept.getService()))
+        {
+            throw new OrchestraException(
+                "FlowCall service [" + flowCall.getService()
+                + "] does not match FlowAccept service [" + flowAccept.getService() + "]");
+        }
+
+        List errors = new ArrayList();
+
+        // check input params
+        List callParamNames = new ArrayList();
+        for(Iterator i = flowCall.getParams().iterator(); i.hasNext(); )
+        {
+            FlowParamSend p = (FlowParamSend) i.next();
+            callParamNames.add(p.getName());
+        }
+
+        for(Iterator i = flowAccept.getParams().iterator(); i.hasNext(); )
+        {
+            FlowParamAccept p = (FlowParamAccept) i.next();
+            String name = p.getName();
+            if (!callParamNames.remove(name) && (p.getDflt() == null))
+            {
+                // accept param does not have call param equivalent, and
+                // there is no default parameter value defined.
+                errors.add(name);
+            }
+        }
+        // add any leftover call params that do not have accept param equivalents
+        errors.addAll(callParamNames);
+
+        if (!errors.isEmpty())
+        {
+            throw new OrchestraException("Parameter names mismatch:" + StringUtils.join(errors.iterator(), ","));
+        }
+
+        // check return params
+        List returnParamNames = new ArrayList();
+        for(Iterator i = flowAccept.getReturns().iterator(); i.hasNext(); )
+        {
+            FlowReturnSend p = (FlowReturnSend) i.next();
+            returnParamNames.add(p.getName());
+        }
+
+        for(Iterator i = flowCall.getReturns().iterator(); i.hasNext(); )
+        {
+            FlowReturnAccept p = (FlowReturnAccept) i.next();
+            String name = p.getName();
+            if (!returnParamNames.remove(name))
+            {
+                // accept param does not have call param equivalent
+                errors.add(name);
+            }
+        }
+        // add any leftover return params that do not have accept return param equivalents
+        errors.addAll(returnParamNames);
+
+        if (!errors.isEmpty())
+        {
+            throw new OrchestraException("Return parameter names mismatch:" + StringUtils.join(errors.iterator(), ","));
+        }
+
+        // all ok
+        return;
+    }
+
+    // no instances of this class expected
+    private FlowHandler()
+    {
+    }
+}

Propchange: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowHandler.java.sk
------------------------------------------------------------------------------
    svn:executable = *

Modified: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowInfo.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowInfo.java?rev=675860&r1=675859&r2=675860&view=diff
==============================================================================
--- myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowInfo.java (original)
+++ myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowInfo.java Fri Jul 11 01:21:30 2008
@@ -25,6 +25,12 @@
 import org.apache.myfaces.orchestra.flow.config.FlowAccept;
 import org.apache.myfaces.orchestra.flow.config.FlowCall;
 
+/**
+ * Holds information about a specific call to a specific flow.
+ * <p>
+ * A new instance of this type is created for each call to a flow,
+ * and is stored in the ConversationContext created for that flow.
+ */
 public class FlowInfo
 {
     // caller info
@@ -48,10 +54,20 @@
      * created a call to setAcceptInfo will be made to add information about the
      * called flow.
      *  
-     * @param callerViewId
-     * @param callerViewRoot
-     * @param flowCall
-     * @param data
+     * @param callerViewId contains the viewId of the view that started the call. This
+     * value must not be null.
+     * 
+     * @param callerViewRoot contains the view tree for the view that started the call.
+     * This is optional; when not null then this view state will be restored on return
+     * from the flow. In particular, this allows data entered by the user into input
+     * components to be restored when the flow returns (assumes that the flow call is
+     * triggered by an immediate command component). When this is null, then on return 
+     * to the caller a new view tree will be created.
+     * 
+     * @param flowCall is the configuration object that describes the caller of a flow.
+     * 
+     * @param data is the set of parameter values being passed to the called flow. See
+     * FlowCall.readSendParams().
      */
     public FlowInfo(String callerViewId, UIViewRoot callerViewRoot, FlowCall flowCall, Map data)
     {
@@ -62,12 +78,19 @@
         
         // todo: save the serialized view tree, not just a ref to the viewroot
     }
-    
+
+    /**
+     * Return the viewId of the page that initiated the call to a flow (never null).
+     */
     public String getCallerViewId() 
     {
         return callerViewId;
     }
     
+    /**
+     * Return the view root of the page that initiated the call to a flow (optional,
+     * may be null).
+     */
     public UIViewRoot getCallerViewRoot() 
     {
         return callerViewRoot;

Modified: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java?rev=675860&r1=675859&r2=675860&view=diff
==============================================================================
--- myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java (original)
+++ myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java Fri Jul 11 01:21:30 2008
@@ -18,13 +18,8 @@
  */
 package org.apache.myfaces.orchestra.flow;
 
-import java.io.IOException;
-
-import javax.faces.FacesException;
 import javax.faces.application.NavigationHandler;
-import javax.faces.application.ViewHandler;
 import javax.faces.component.UIViewRoot;
-import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
 
 import org.apache.commons.logging.Log;
@@ -36,88 +31,50 @@
 
     private NavigationHandler delegate;
 
+    /**
+     * Constructor.
+     */
     public FlowNavigationHandler(NavigationHandler delegate)
     {
         this.delegate = delegate;
     }
 
-    // WARNING: when doing a "commit" or "cancel" of a flow, the underlying navigation handler
-    // is never called; we leap directly back to the flow that called the one that just ended.
-    //
-    // TODO: what about restoring state that is not in the view tree, eg FacesMessage objects
-    // (which are attached to a FacesContext)? I guess if this is important then it can also
-    // be attached to the FlowInfo object...
-    //
-    // TODO: what about RedirectTracker's functionality to save and restore messages,
-    // request-scoped beans etc? Presumably that will not run. So are there any cases where
-    // redirectTracker and "commit/cancel" have unexpected behaviour? Probably yes if a
-    // page with request-scoped beans does a redirect to a flow start. The RedirectTracker
-    // will save request-scoped beans and messages when leaving the caller page. Then it
-    // will try to restore them into the child context (yecch). On return to the caller,
-    // if it runs *earlier* then it will try to copy stuff from the flow exit page back to
-    // the caller; if it runs later then it never runs at all. 
-    //
-    // TODO: what about the orchestra custom NavigationHandler? --> that is no longer
-    // used...
+    /**
+     * Special handleNavigation processing for Orchestra Flows.
+     * <p>
+     * In most cases this method simply delegates to the wrapped instance to perform a
+     * normal navigation. However there are the following exceptions:
+     * <ul>
+     * <li>When a flow is active, and the outcome matches the "commit" outcome for the flow
+     * <li>When a flow is active, and the outcome matches the "cancel" outcome for the flow
+     * </ul>
+     * Note that in these special cases, the underlying navigation handler is never called;
+     * we leap directly back to the clow that called the one that just ended.
+     * <p>
+     * There are also some special logic that needs to be triggered when the navigation
+     * outcome matches a "flowCall" value for the current view.
+     */
     public void handleNavigation(FacesContext facesContext, String fromAction, String outcome)
     {
+        // TODO: what about restoring state that is not in the view tree, eg FacesMessage objects
+        // (which are attached to a FacesContext)? I guess if this is important then it can also
+        // be attached to the FlowInfo object...
+        //
+        // TODO: what about RedirectTracker's functionality to save and restore messages,
+        // request-scoped beans etc? Presumably that will not run. So are there any cases where
+        // redirectTracker and "commit/cancel" have unexpected behaviour? Probably yes if a
+        // page with request-scoped beans does a redirect to a flow start. The RedirectTracker
+        // will save request-scoped beans and messages when leaving the caller page. Then it
+        // will try to restore them into the child context (yecch). On return to the caller,
+        // if it runs *earlier* then it will try to copy stuff from the flow exit page back to
+        // the caller; if it runs later then it never runs at all. 
+
         UIViewRoot currViewRoot = facesContext.getViewRoot();
         String oldViewId = currViewRoot.getViewId();
         log.debug("handleNavigation: view=" + oldViewId + ",outcome=" + outcome);
 
-        FlowHandler.Selection sel = new FlowHandler.Selection();
-        FlowHandler.processCall(sel, facesContext, oldViewId, outcome);
-
-        if (sel.outcome != null)
-        {
-            // Doing custom navigation. We need to have the current view root set
-            // correctly so that the navigation handler selects the right nav-case.
-            //
-            // The UIViewRoot installed also needs a locale and renderKitId, as the
-            // NavHandler will call ViewHandler.createView, which will try to copy
-            // those values onto the new UIViewRoot it creates.
-            UIViewRoot dummyViewRoot = new UIViewRoot();
-            dummyViewRoot.setViewId(sel.viewId);
-            dummyViewRoot.setLocale(currViewRoot.getLocale());
-            dummyViewRoot.setRenderKitId(currViewRoot.getRenderKitId());
-            facesContext.setViewRoot(dummyViewRoot);
-            delegate.handleNavigation(facesContext, fromAction, sel.outcome);
-            return;
-        }
-
-        if (sel.viewRoot != null)
-        {
-            // We are leaping back to a previous view, and have a saved version of
-            // the view tree. The processCall method will already have destroyed the
-            // child context, so just render the specified view.
-            facesContext.setViewRoot(sel.viewRoot);
-            facesContext.renderResponse();
-            return;
-        }
-
-        if (sel.viewId != null)
-        {
-            // We are leaping back to a previous view, but have no saved version of
-            // the view tree. The processCall method will already have destroyed the
-            // child context, so just send a redirect to the specified view. Using
-            // a redirect here nicely updates the browser URL bar. Sometimes an
-            // internal forward might be better though; how can we tell I wonder..
-            ExternalContext externalContext = facesContext.getExternalContext();
-            ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
-            String redirectPath = viewHandler.getActionURL(facesContext, sel.viewId);
-
-            try
-            {
-                externalContext.redirect(externalContext.encodeActionURL(redirectPath));
-            }
-            catch (IOException e)
-            {
-                throw new FacesException(e.getMessage(), e);
-            }
-            return;
-        }
-
-        // Navigation is not being overruled, so do normal operation
-        delegate.handleNavigation(facesContext, fromAction, outcome);
+        FlowNavigator nav = new FlowNavigator(facesContext, delegate, fromAction, outcome);
+        FlowHandler.processCall(nav, facesContext, oldViewId, outcome);
+        nav.doNavigation();
     }
 }

Added: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java.sk
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java.sk?rev=675860&view=auto
==============================================================================
--- myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java.sk (added)
+++ myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java.sk Fri Jul 11 01:21:30 2008
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.orchestra.flow;
+
+import javax.faces.application.NavigationHandler;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.FacesContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class FlowNavigationHandler extends NavigationHandler
+{
+    private static final Log log = LogFactory.getLog(FlowNavigationHandler.class);
+
+    private NavigationHandler delegate;
+
+    public FlowNavigationHandler(NavigationHandler delegate)
+    {
+        this.delegate = delegate;
+    }
+
+    // WARNING: when doing a "commit" or "cancel" of a flow, the underlying navigation handler
+    // is never called; we leap directly back to the flow that called the one that just ended.
+    //
+    // TODO: what about restoring state that is not in the view tree, eg FacesMessage objects
+    // (which are attached to a FacesContext)? I guess if this is important then it can also
+    // be attached to the FlowInfo object...
+    //
+    // TODO: what about RedirectTracker's functionality to save and restore messages,
+    // request-scoped beans etc? Presumably that will not run. So are there any cases where
+    // redirectTracker and "commit/cancel" have unexpected behaviour? Probably yes if a
+    // page with request-scoped beans does a redirect to a flow start. The RedirectTracker
+    // will save request-scoped beans and messages when leaving the caller page. Then it
+    // will try to restore them into the child context (yecch). On return to the caller,
+    // if it runs *earlier* then it will try to copy stuff from the flow exit page back to
+    // the caller; if it runs later then it never runs at all. 
+    //
+    // TODO: what about the orchestra custom NavigationHandler? --> that is no longer
+    // used...
+    public void handleNavigation(FacesContext facesContext, String fromAction, String outcome)
+    {
+        UIViewRoot currViewRoot = facesContext.getViewRoot();
+        String oldViewId = currViewRoot.getViewId();
+        log.debug("handleNavigation: view=" + oldViewId + ",outcome=" + outcome);
+
+        Navigator nav = new Navigator(facesContext, delegate, fromAction);
+        FlowHandler.processCall(nav, facesContext, oldViewId, outcome);
+    }
+}

Propchange: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigationHandler.java.sk
------------------------------------------------------------------------------
    svn:executable = *

Added: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigator.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigator.java?rev=675860&view=auto
==============================================================================
--- myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigator.java (added)
+++ myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigator.java Fri Jul 11 01:21:30 2008
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.orchestra.flow;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.faces.FacesException;
+import javax.faces.application.NavigationHandler;
+import javax.faces.application.ViewHandler;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+
+/**
+ * Provides methods that can be invoked to do various types of JSF
+ * page navigation.
+ * <p>
+ * Internally this wraps a standard JSF NavigationHandler object.
+ */
+public class FlowNavigator
+{
+    private FacesContext facesContext;
+    private NavigationHandler delegate;
+    private String fromAction;
+    private String outcome;
+    private boolean navigationDone = false;
+
+    /**
+     * Constructor that initialises this instance with all the info it needs to
+     * do a normal navigation operation. 
+     * <p>
+     * If some kind of special navigation is desired (eg due to flow return) then the
+     * extra data is provided in the call to the relevant method.
+     */
+    public FlowNavigator(FacesContext facesContext, NavigationHandler delegate, String fromAction, String outcome)
+    {
+        this.facesContext = facesContext;
+        this.delegate = delegate;
+        this.fromAction = fromAction;
+        this.outcome = outcome;
+    }
+
+    /**
+     * Forces a navigation to the specified viewRoot (if not null), or to the
+     * specified viewId if the viewRoot is null.
+     * <p>
+     * This is expected to be called when a flow is cancelled or committed,
+     * in order to return to the calling view (whose view state might or
+     * might not have been saved when the flow was originally called).
+     */
+    public void goToView(UIViewRoot viewRoot, String viewId)
+    {
+        if (viewRoot != null)
+        {
+            // We are leaping back to a previous view, and have a saved version of
+            // the view tree. The processCall method will already have destroyed the
+            // child context, so just render the specified view.
+            facesContext.setViewRoot(viewRoot);
+            facesContext.renderResponse();
+        }
+        else
+        {
+            // We are leaping back to a previous view, but have no saved version of
+            // the view tree. Using a redirect here nicely updates the browser URL
+            // bar. Sometimes an internal forward might be preferred though; how
+            // can we tell I wonder..
+            ExternalContext externalContext = facesContext.getExternalContext();
+            ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
+            String redirectPath = viewHandler.getActionURL(facesContext, viewId);
+
+            try
+            {
+                externalContext.redirect(externalContext.encodeActionURL(redirectPath));
+            }
+            catch (IOException e)
+            {
+                throw new FacesException(e.getMessage(), e);
+            }
+        }
+        navigationDone = true;
+    }
+
+    /**
+     * Forces a navigation by outcome to occur relative to the specified
+     * view.
+     * <p>
+     * When viewRoot is non-null, then that is used as the base view for
+     * looking up the outcome. When null, then viewId is used.
+     * <p>
+     * This is expected to be called when a flow is committed and a custom
+     * onCommit method returns a navigation outcome.
+     */
+    public void goToOutcome(UIViewRoot viewRoot, String viewId, String outcome)
+    {
+        // We *must* set a view root in order for the navigation case lookup
+        // to work correctly; the current view root is used as the "from-view-id"
+        // in the lookup. When we have the original view state to restore, that
+        // is no problem. But when we only have an original viewId, then we must
+        // create a dummy viewRoot with that viewId before invoking the navigation
+        // handler.
+
+        if (viewRoot != null)
+        {
+            facesContext.setViewRoot(viewRoot);
+        }
+        else
+        {
+            // Create dummy viewRoot with the right viewId so that navigation
+            // uses the desired from-view-id during navigation case lookup.
+            //
+            // Unfortunately when the navigation rule matched is not a forward,
+            // then ViewHandler.createView is executed and that expects to be able to
+            // copy properties locale and renderKitId from the current view root into
+            // the new view root. So we need to ensure that those properties are also
+            // correctly set up on our dummy view root.
+
+            Locale locale;
+            String renderKitId;
+
+            if (facesContext.getViewRoot() != null)
+            {
+                UIViewRoot currViewRoot = facesContext.getViewRoot();
+                locale = currViewRoot.getLocale();
+                renderKitId = currViewRoot.getRenderKitId();
+            }
+            else
+            {
+                ViewHandler vh = facesContext.getApplication().getViewHandler();
+                locale = vh.calculateLocale(facesContext);
+                renderKitId = vh.calculateRenderKitId(facesContext);
+            }
+
+            UIViewRoot dummyViewRoot = new UIViewRoot();
+            dummyViewRoot.setViewId(viewId);
+            dummyViewRoot.setLocale(locale);
+            dummyViewRoot.setRenderKitId(renderKitId);
+            facesContext.setViewRoot(dummyViewRoot);
+        }
+
+        delegate.handleNavigation(facesContext, "flowCommit", outcome);
+        navigationDone = true;
+    }
+
+    /**
+     * If special navigation has not yet been caused by calls to other methods on this class,
+     * then do a normal navigation operation.
+     */
+    public void doNavigation()
+    {
+        if (!navigationDone)
+        {
+            // Navigation is not being overruled, so do normal operation
+            delegate.handleNavigation(facesContext, fromAction, outcome);
+            navigationDone = true;
+        }
+    }
+}

Propchange: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigator.java
------------------------------------------------------------------------------
    svn:executable = *

Added: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/Navigator.java.sk
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/Navigator.java.sk?rev=675860&view=auto
==============================================================================
--- myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/Navigator.java.sk (added)
+++ myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/Navigator.java.sk Fri Jul 11 01:21:30 2008
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.orchestra.flow;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.faces.FacesException;
+import javax.faces.application.NavigationHandler;
+import javax.faces.application.ViewHandler;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+
+public class Navigator
+{
+    private FacesContext facesContext;
+    private NavigationHandler delegate;
+    private String fromAction;
+
+    public Navigator(FacesContext facesContext, NavigationHandler delegate, String fromAction)
+    {
+        this.facesContext = facesContext;
+        this.delegate = delegate;
+        this.fromAction = fromAction;
+    }
+
+    public void goToView(String viewId, UIViewRoot viewRoot)
+    {
+        if (viewRoot != null)
+        {
+            // We are leaping back to a previous view, and have a saved version of
+            // the view tree. The processCall method will already have destroyed the
+            // child context, so just render the specified view.
+            facesContext.setViewRoot(viewRoot);
+            facesContext.renderResponse();
+        }
+        else
+        {
+            // We are leaping back to a previous view, but have no saved version of
+            // the view tree. The processCall method will already have destroyed the
+            // child context, so just send a redirect to the specified view. Using
+            // a redirect here nicely updates the browser URL bar. Sometimes an
+            // internal forward might be better though; how can we tell I wonder..
+            ExternalContext externalContext = facesContext.getExternalContext();
+            ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
+            String redirectPath = viewHandler.getActionURL(facesContext, viewId);
+
+            try
+            {
+                externalContext.redirect(externalContext.encodeActionURL(redirectPath));
+            }
+            catch (IOException e)
+            {
+                throw new FacesException(e.getMessage(), e);
+            }
+        }
+    }
+
+    // Doing custom navigation. We need to have the current view root set
+    // correctly so that the navigation handler selects the right nav-case.
+    //
+    // The UIViewRoot installed also needs a locale and renderKitId, as the
+    // NavHandler will call ViewHandler.createView, which will try to copy
+    // those values onto the new UIViewRoot it creates.
+    public void goToOutcome(String viewId, UIViewRoot viewRoot, String outcome)
+    {
+        Locale locale;
+        String renderKitId;
+
+        if (viewRoot != null)
+        {
+            locale = viewRoot.getLocale();
+            renderKitId = viewRoot.getRenderKitId();
+        }
+        else if (facesContext.getViewRoot() != null)
+        {
+            UIViewRoot currViewRoot = facesContext.getViewRoot();
+            locale = currViewRoot.getLocale();
+            renderKitId = currViewRoot.getRenderKitId();
+        }
+        else
+        {
+            ViewHandler vh = facesContext.getApplication().getViewHandler();
+            locale = vh.calculateLocale(facesContext);
+            renderKitId = vh.calculateRenderKitId(facesContext);
+        }
+
+        UIViewRoot dummyViewRoot = new UIViewRoot();
+        dummyViewRoot.setViewId(viewId);
+        dummyViewRoot.setLocale(locale);
+        dummyViewRoot.setRenderKitId(renderKitId);
+        facesContext.setViewRoot(dummyViewRoot);
+
+        delegate.handleNavigation(facesContext, "flowCommit", outcome);
+    }
+
+    public void goToOutcome(String outcome)
+    {
+        // Navigation is not being overruled, so do normal operation
+        delegate.handleNavigation(facesContext, fromAction, outcome);
+    }
+}

Propchange: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/Navigator.java.sk
------------------------------------------------------------------------------
    svn:executable = *