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/10/15 13:39:50 UTC

svn commit: r704866 - in /myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow: FlowHandler.java FlowNavigationHandler.java FlowNavigator.java FlowViewHandler.java

Author: skitching
Date: Wed Oct 15 04:39:49 2008
New Revision: 704866

URL: http://svn.apache.org/viewvc?rev=704866&view=rev
Log:
Always use redirect at flow entry and exit, so browser URL reflects new path and new conversationContext value.
Add initial support for modal windows.

Removed:
    myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowNavigator.java
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/FlowNavigationHandler.java
    myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowViewHandler.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=704866&r1=704865&r2=704866&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 Wed Oct 15 04:39:49 2008
@@ -18,21 +18,26 @@
  */
 package org.apache.myfaces.orchestra.flow;
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import javax.faces.FacesException;
+import javax.faces.application.ViewHandler;
 import javax.faces.component.UIViewRoot;
 import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
+import javax.servlet.http.HttpServletResponse;
 
 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.components.ModalFlow;
 import org.apache.myfaces.orchestra.flow.config.FlowAccept;
 import org.apache.myfaces.orchestra.flow.config.FlowCall;
 import org.apache.myfaces.orchestra.flow.config.FlowConfig;
@@ -52,6 +57,7 @@
 public class FlowHandler
 {
     private static final Log log = LogFactory.getLog(FlowHandler.class);
+    private static final String VIEW_TO_RESTORE_KEY = FlowHandler.class.getName() + ":viewToRestore";
 
     /**
      * Return the FlowCall object associated with the current conversation
@@ -110,6 +116,74 @@
         return fa;
     }
 
+
+    /**
+     * 
+     * @param facesContext
+     * @param viewId
+     * @param flowInfo
+     */
+    private static void redirectTo(FacesContext facesContext, String viewId, FlowInfo flowInfo)
+    {
+        ExternalContext ec = facesContext.getExternalContext();
+        ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
+        String url = viewHandler.getActionURL(facesContext, viewId);
+        url = ec.encodeActionURL(url);
+        
+        if (!flowInfo.isModalFlow())
+        {
+            try
+            {
+                ec.redirect(url);
+                return;
+            }
+            catch(IOException ioe)
+            {
+                throw new FacesException("Unable to redirect to " + viewId, ioe);
+            }
+        }
+        
+        // Ok, we are closing a modal flow. We therefore need to render some javascript that
+        // will close the modal window and force the parent window to fetch the appropriate url.
+        //
+        // Note that we cannot allow navigation to occur then send the redirect in the createView
+        // method because we would need a valid outcome to return here; without that the NavigationHandler
+        // just does a "null" navigation, ie returns the current UIViewRoot and never calls the ViewHandler
+        // object.
+        //
+        // And we cannot re-render the popup window because that would need the child context to be
+        // active in order to work in all cases (though re-rendering the popup window after the postback
+        // that closes it might itself cause problems eg reuse of committed persistent objects). But
+        // if we need the child context in order to render the child view, then when do we destroy it?
+        //
+        // So here we do a truly ugly hack: send some raw html to the response stream right here.
+        // The major problem here is that this assumes an HTML renderkit. Well, actually the below
+        // is also valid XHTML too..
+        StringBuffer buf = new StringBuffer();
+        buf.append("<html><head><script>//<![CDATA[\n");
+        buf.append("var url='"+ url + "';\n");
+        buf.append(flowInfo.getOnExitScript());
+        buf.append("\n//]]>\n");
+        buf.append("</script></head></html>");
+        try
+        {
+            // Here we cannot use facesContext.getResponseWriter(). That returns null
+            // until after ViewHandler.renderView has been invoked. So here we use
+            // the raw response stream. Hey, all this html is so hacky it seems no
+            // worse to access the response stream here.
+            //
+            // Of course this won't work with portlets, but the idea of a portlet popping
+            // up a modal window to invoke a flow is rather unlikely anyway..
+            HttpServletResponse rsp = (HttpServletResponse) ec.getResponse();
+            rsp.getWriter().write(buf.toString());
+        }
+        catch(IOException ioe)
+        {
+            throw new FacesException(ioe);
+        }
+        facesContext.responseComplete();
+    }
+
     /**
      * Do actions that do not depend on properties of the called flow (if any).
      * <p>
@@ -124,8 +198,12 @@
      * 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>.
+     *
+     * @return a navigation outcome string to pass to the NavigationHandler. Note
+     * however that if this method has sent a redirect then the return value will
+     * simply be ignored.
      */
-    static void processPreNav(FlowNavigator nav, FacesContext facesContext, String oldViewId, String outcome)
+    static String processPreNav(FacesContext facesContext, String oldViewId, String outcome)
     {
         log.debug("processCall: [" + String.valueOf(oldViewId) + "] outcome: " + outcome);
 
@@ -152,8 +230,12 @@
                 // Mark flowInfo as cancelled
                 flowInfo.cancel();
 
-                nav.returnToView(callerViewRoot, callerViewId);
-                return;
+                // Store viewRoot from flowInfo into parent context
+                parent.setAttribute(VIEW_TO_RESTORE_KEY, callerViewRoot);
+
+                // And redirect. This sets responseComplete, so navigation is skipped.
+                redirectTo(facesContext, callerViewId, flowInfo);
+                return outcome;
             }
 
             if (outcome.equals(fa.getCommitWhen()))
@@ -178,27 +260,50 @@
                 cm.activateConversationContext(parent);
                 cm.removeAndInvalidateConversationContext(ctx);
 
-                // Now restore the view tree of the caller. 
-                nav.returnToView(callerViewRoot, callerViewId);
+                // Now restore the view tree of the caller.
+                facesContext.setViewRoot(callerViewRoot);
 
                 // And with the original context active, write parameters back then
                 // invoke any custom actions the caller wants to run.
                 //
                 // Note that because the original view has already been restored,
-                // the caller can safely access stuff in its view. 
+                // the caller can safely access stuff in its view.
+                //
+                // Note that the ViewController methods are not invoked even though
+                // the backing bean for the view is. This is not a problem really, just
+                // something to be aware of.
                 flowInfo.getFlowCall().writeAcceptParams(facesContext, map);
                 FlowOnCommit onCommit = flowInfo.getFlowCall().getOnCommit();
-                if (onCommit != null)
+                if (onCommit == null)
+                {
+                    // Store viewRoot from flowInfo into parent context
+                    parent.setAttribute(VIEW_TO_RESTORE_KEY, callerViewRoot);
+                    redirectTo(facesContext, callerViewId, flowInfo);
+                }
+                else
                 {
                     Object actionOutcome = onCommit.execute(facesContext);
-                    if (actionOutcome != null)
+                    if (actionOutcome == null)
+                    {
+                        parent.setAttribute(VIEW_TO_RESTORE_KEY, callerViewRoot);
+                        redirectTo(facesContext, callerViewId, flowInfo);
+                    }
+                    else
                     {
-                        nav.goToOutcome(callerViewRoot, callerViewId, actionOutcome.toString());
-                        return;
+                        // Compute the viewId that this outcome maps to, then redirect to it. Note that
+                        // we have restored the caller's view so navigation rules for the caller's view
+                        // are used.
+                        //
+                        // Tricky: any attempt to compute the new viewId will trigger createView. We therefore
+                        // need to set a flag telling the ViewHandler to just do a redirect in createView. Ugly,
+                        // and might have problems when other custom ViewHandlers are also configured, but JSF
+                        // gives us no option here.
+                        facesContext.getExternalContext().getRequestMap().put("isRedirectOnReturn", Boolean.TRUE);
+                        return actionOutcome.toString();
                     }
                 }
 
-                return;
+                return outcome;
             }
         }
 
@@ -213,16 +318,38 @@
             // Build a FlowInfo object for the called flow to use.
             flowInfo = new FlowInfo(oldViewId, facesContext.getViewRoot(), flowCall, data);
             
-            // create child context
+            // create child context and activate it
             ConversationManager cm = ConversationManager.getInstance(true);
             ConversationContext parent = cm.getCurrentConversationContext();
             ConversationContext child = cm.createConversationContext(parent);
+            cm.activateConversationContext(child);
 
+            // Handle triggering of modalflow if any
+            ModalFlow f = ModalFlow.getForOutcome(facesContext, outcome);
+            if (f != null)
+            {
+                // The current view contains a ModalFlow component that declares that it will handle
+                // the new flow by opening the entry page in a new frame or window. We mark that component
+                // as "active" so that the component renders javascript to open that window. We also mark
+                // the FlowInfo as modal; this is checked in FlowViewHandler.createView.
+                //
+                // TODO: also handle <redirect/> rules, which instead invoke the
+                // ExternalContext.redirect method rather than ViewHandler.createView?
+                flowInfo.setModalFlow(true);
+                flowInfo.setOnExitScript(f.getOnExit());
+                f.setActive(true);
+            }
+
+            // Store the flowInfo in the child context
             child.setAttribute("flowInfo", flowInfo);
-            cm.activateConversationContext(child);
 
-            nav.callFlow(outcome, flowInfo);
+            // And let normal navigation occur. Hopefully the new page will be the entry-point
+            // for a flow, in which case we finish setting up the FlowInfo object then redirect
+            // to the flow entry point; see FlowViewHander.createView. If something goes wrong,
+            // then FlowViewHandler.createView will clean up.
         }
+        
+        return outcome;
     }
 
     /**
@@ -275,6 +402,11 @@
         return false;
     }
 
+    private static class ViewHolder
+    {
+        UIViewRoot root;
+    }
+
     /**
      * Handle case where we have just navigated to a new page, and the page that caused the
      * navigation did a flow-call.
@@ -287,7 +419,8 @@
      * 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 doNewFlowEntry(FacesContext facesContext, FlowInfo flowInfo, String newViewId)
+    static boolean doNewFlowEntry(FacesContext facesContext, ViewHandler viewHandler,
+            ViewHolder viewHolder, FlowInfo flowInfo, String newViewId)
     {
         if (flowInfo == null)
         {
@@ -307,8 +440,12 @@
         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");
+            StringBuffer msg = new StringBuffer();
+            msg.append("Invocation of flow without callee declaration. ");
+            msg.append("Calling view is " + flowInfo.getCallerViewId() + ". ");
+            msg.append("Called view is " + newViewId + ".");
+            log.debug("isNewFlowEntry: Error: " + msg.toString());
+            throw new OrchestraException(msg.toString());
         }
 
         log.debug("isNewFlowEntry: new flow detected.");
@@ -323,35 +460,179 @@
         Map argsIn = flowInfo.getArgsIn();
         flowAccept.writeAcceptParams(facesContext, argsIn);
 
-        return true;
+        ExternalContext externalContext = facesContext.getExternalContext();
+        
+        // The "postback" part of the current request must have started a flow call.
+        if (flowInfo.isModalFlow())
+        {
+            // First compute the URL for the *new* viewId (the flow entry page) while the child
+            // context is active so the conversationContext query param gets set right.
+            String newViewUrl = viewHandler.getActionURL(facesContext, newViewId);
+            newViewUrl = externalContext.encodeActionURL(newViewUrl);
+
+            // Verify that this really is the entry point for a new flow.
+            // Do import On error, reset
+            // context to parent then throw exception.
+            // And copy input parameters for the 
+            // Now we want to re-render the *old* view, so we need to activate the parent
+            // context for the remainder of this request. Note that the ConversationManager
+            // must exist as we have a FlowInfo object.
+            ConversationManager cm = ConversationManager.getInstance();
+            ConversationContext child = cm.getCurrentConversationContext();
+            ConversationContext parent = child.getParent();
+            cm.activateConversationContext(parent);
+
+            // Set some variables that allows components in the page to
+            // access the URL that the new modal window should fetch.
+            setNewViewInfo(newViewId, newViewUrl);
+
+            // And just return the current view rather than creating a new one.
+            viewHolder.root = facesContext.getViewRoot();
+            return true;
+        }
+
+        // Force a redirect to the entry page for the flow. Note that the currently activated context
+        // is the child one, so getActionURL encodes the conversationContext value appropriately.
+        String newViewUrl = viewHandler.getActionURL(facesContext, newViewId);
+        newViewUrl = externalContext.encodeActionURL(newViewUrl);
+        try
+        {
+            facesContext.getExternalContext().redirect(newViewUrl);
+            return true;
+        }
+        catch(IOException ioe)
+        {
+            throw new FacesException("Unable to enter flow", ioe);
+        }
+        
+    }
+
+    private static Object rmvContextAttribute(String id)
+    {
+        ConversationManager cm = ConversationManager.getInstance(false);
+        if (cm == null)
+        {
+            return null;
+        }
+        
+        ConversationContext ctx = cm.getCurrentConversationContext();
+        if (ctx == null)
+        {
+            return null;
+        }
+        
+        Object o = ctx.removeAttribute(id);
+        return o;
+    }
+
+    private static void setNewViewInfo(String viewId, String viewUrl)
+    {
+        FacesContext fc = FacesContext.getCurrentInstance();
+        Map reqMap = fc.getExternalContext().getRequestMap();
+        reqMap.put("orchestraFlowId", viewId);
+        reqMap.put("orchestraFlowUrl", viewUrl);
     }
 
     /**
-     * Invoked to handle actions that require info about the called page, but not the caller.
+     * Special createView handling for Orchestra Flows.
+     * <p>
+     * When this request was a postback that started a flowcall:
+     * <ul>
+     * <li>validate that this new view is a suitable flow entry point
+     * <li>import passed parameters into child context
+     * <li>for non-modal call: send a redirect to this view
+     * <li>for modal call: rerender the *old* view, which should then
+     * trigger a GET to the new view in a new window or frame.
+     * </ul>
+     * <p>
+     * When this request was a postback that started a flowreturn:
+     * <ul>
+     * <li>Normally the view to return to is known so this code is not executed.
+     * <li>When the view to return to has a "return handler" that provides 
+     * a nav-outcome then this code is executed; just redirect to the desired view.
+     * </ul>
+     * <p>
+     * Throws OrchestraException if there is a flow error, eg if the view
+     * specified is the entry point for a flow but the previous view did
+     * not do a FlowCall.
+     *
+     * @return a UIViewRoot to use when rendering this request. If null is returned
+     * then the standard ViewHandler.createView method is used to create one. Note
+     * however that if this method has sent a redirect then the return value will
+     * simply be ignored.
      */
-    static void processPreCreateView(FacesContext facesContext, String newViewId)
+    static UIViewRoot processPreCreateView(FacesContext facesContext, ViewHandler viewHandler, String newViewId)
     {
         log.debug("processAccept: [" + newViewId + "]");
+
         FlowInfo flowInfo = getFlowInfo();
+
+        boolean isRedirectOnReturn = facesContext.getExternalContext().getRequestMap()
+                                        .containsKey("redirectOnReturn");
+        if (isRedirectOnReturn)
+        {
+            // The current request contained a postback that triggered a flow-return. The caller also had a
+            // return-handler that returned an outcome to navigate to, ie the caller wants an immediate
+            // bounce to somewhere else rather than redisplaying itself. We couldn't compute the url to redirect
+            // to earlier due to the brain-dead NavigationHandler api, so have to handle it here.
+            //
+            // Force a redirect to the entry page for the flow. Note that the currently activated context
+            // is the child one, so getActionURL encodes the conversationContext value appropriately.
+            ExternalContext ec = facesContext.getExternalContext();
+            String newViewUrl = viewHandler.getActionURL(facesContext, newViewId);
+            newViewUrl = ec.encodeActionURL(newViewUrl);
+            try
+            {
+                ec.redirect(newViewUrl);
+                return null;
+            }
+            catch(IOException ioe)
+            {
+                throw new FacesException("Unable to enter flow", ioe);
+            }
+        }
         
-        if (doNewFlowEntry(facesContext, flowInfo, newViewId))
+        UIViewRoot viewToRestore = (UIViewRoot) rmvContextAttribute(VIEW_TO_RESTORE_KEY);
+        if (viewToRestore != null)
+        {
+            // The previous request triggered a flowreturn, ie this request is re-rendering the calling view.
+            // We therefore may have a cached viewRoot object that holds the view saved when the call was made..
+            //
+            // TODO: possibly check that viewToRestore.viewId == newViewId. This should always be true, as we
+            // only set that attribute immediately before sending a redirect to the matching id.
+            return viewToRestore;
+        }
+
+        ViewHolder viewHolder = new ViewHolder();
+        if (doNewFlowEntry(facesContext, viewHandler, viewHolder, flowInfo, newViewId))
         {
-            return;
+            // We have just entered a new flow
+            return viewHolder.root;
         }
 
         if (isAbnormalFlowExit(flowInfo, newViewId))
         {
-            return;
+            // User has leapt out of an existing flow
+            return null;
         }
 
+        // Below here are just sanity checks, looking for weird situations..
+
         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");
+            // The new page is a flow entry point, but the earlier page did a normal navigation,
+            // without any flow-call. Therefore we do not have a child context set up, and no
+            // FlowInfo object. Unfortunately we therefore do not know what the previous
+            // page was...
+            StringBuffer msg = new StringBuffer();
+            msg.append("Invocation of flow without caller declaration. ");
+            msg.append("Called view is " + newViewId + ".");
+            log.debug("processAccept: Error: " + msg.toString());
+            throw new OrchestraException(msg.toString());
         }
         
 
@@ -379,6 +660,9 @@
                 throw new OrchestraException("Child flow not properly terminated.");
             }
         }
+
+        // all ok, let view be created in normal manner
+        return null;
     }
 
     /**
@@ -432,7 +716,7 @@
      * Return the FlowInfo object stored in the current conversation
      * context (if any).
      */
-    private static FlowInfo getFlowInfo()
+    static FlowInfo getFlowInfo()
     {
         ConversationManager cm = ConversationManager.getInstance(false);
         if (cm == null)

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=704866&r1=704865&r2=704866&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 Wed Oct 15 04:39:49 2008
@@ -40,19 +40,8 @@
     }
 
     /**
-     * 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 flow 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.
+     * Ensure that the FlowHandler gets the chance to run after the navigation outcome has been
+     * determined.
      */
     public void handleNavigation(FacesContext facesContext, String fromAction, String outcome)
     {
@@ -60,21 +49,25 @@
         // (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. 
+        // Note that the Tomahawk RedirectTracker is no problem here. It triggers only when a JSF
+        // navigation-rule specifies the <redirect/> tag, and we never expect that to
+        // occur for rules that navigate to the start of a new flow. So RedirectTracker
+        // and Flow effectively have no overlap and will not interfere with each other.
 
         UIViewRoot currViewRoot = facesContext.getViewRoot();
         String oldViewId = currViewRoot.getViewId();
         log.debug("handleNavigation: view=" + oldViewId + ",outcome=" + outcome);
 
-        FlowNavigator nav = new FlowNavigator(facesContext, delegate, fromAction, outcome);
-        FlowHandler.processPreNav(nav, facesContext, oldViewId, outcome);
-        nav.doNavigation();
+        outcome = FlowHandler.processPreNav(facesContext, oldViewId, outcome);
+        
+        if (!facesContext.getResponseComplete())
+        {
+            delegate.handleNavigation(facesContext, fromAction, outcome);
+        }
+        
+        // TODO: handle isRedirectOnReturn here rather than in FlowViewHandler.createView.
+        // After handleNavigation has returned, we have the new UIViewRoot object so just need
+        // to invoke sendRedirect here. This would mean that we run "flow entry" code in the
+        // same request as the original, rather than after the redirect. But that seems ok..
     }
 }

Modified: myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowViewHandler.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowViewHandler.java?rev=704866&r1=704865&r2=704866&view=diff
==============================================================================
--- myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowViewHandler.java (original)
+++ myfaces/orchestra/trunk/flow/src/main/java/org/apache/myfaces/orchestra/flow/FlowViewHandler.java Wed Oct 15 04:39:49 2008
@@ -21,7 +21,6 @@
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.util.Locale;
-import java.util.Map;
 
 import javax.faces.FacesException;
 import javax.faces.application.ViewHandler;
@@ -175,69 +174,43 @@
     /**
      * Special createView handling for Orchestra Flows.
      * <p>
-     * This method always delegates to the wrapped instance to create the view
-     * that the user asked for (unless we report a flow error), but we first do
-     * flow-related things like:
+     * When this request was a postback that started a flowcall:
      * <ul>
-     * <li>import parameters from calling to called context if this is the
-     * first page of a new flow
-     * <li>discard some conversation contexts if this view is not in the
-     * currently active flow
-     * </li>
+     * <li>validate that this new view is a suitable flow entry point
+     * <li>import passed parameters into child context
+     * <li>for non-modal call: send a redirect to this view
+     * <li>for modal call: rerender the *old* view, which should then
+     * trigger a GET to the new view in a new window or frame.
+     * </ul>
+     * <p>
+     * When this request was a postback that started a flowreturn:
+     * <ul>
+     * <li>Normally the view to return to is known so this code is not executed.
+     * <li>When the view to return to has a "return handler" that provides 
+     * a nav-outcome then this code is executed; just redirect to the desired view.
+     * </ul>
      * <p>
      * Throws OrchestraException if there is a flow error, eg if the view
      * specified is the entry point for a flow but the previous view did
      * not do a FlowCall.
      */
-    public UIViewRoot createView(FacesContext context, String newViewId)
+    public UIViewRoot createView(FacesContext facesContext, String newViewId)
     {
-        if (isCaptureNavigation())
+        UIViewRoot viewRoot = FlowHandler.processPreCreateView(facesContext, delegate, newViewId);
+        if (viewRoot == null)
         {
-            // Cache the target view in the request, but stay on current view.
-            // See documentation for method captureNavigation for more details.
-            String newViewUrl = getActionURL(context, newViewId);
-            setNewViewInfo(newViewId, newViewUrl);
-            return context.getViewRoot();
+            if (facesContext.getResponseComplete())
+            {
+                // We will never use the viewRoot returned from this method because the response
+                // is already complete. However this method is not allowed to return null, so
+                // here we return a dummy root object.
+               viewRoot = new UIViewRoot();
+            }
+            else
+            {
+                viewRoot = delegate.createView(facesContext, newViewId);    
+            }
         }
-        else
-        {
-            FlowHandler.processPreCreateView(context, newViewId);
-            return delegate.createView(context, newViewId);
-        }
-    }
-
-    /**
-     * Enable or disable "navigation capture" for the current request.
-     * <p>
-     * When enabled, method createView will not load a new view, and will simply stay on the current
-     * view. However the information about where it _would_ have navigated to is cached in the request.
-     * <p>
-     * This is intended to be used by "modal dialog" support, so that a page which triggers a new flow
-     * can be re-rendered unaltered and instead (somehow) the new view can be loaded using a separate
-     * request from the browser (in a new window or frame).
-     * <p>
-     * This method must only be called when the current request is a postback, ie there is a
-     * "current view" to stay on.
-     */
-    public static void captureNavigation(boolean state)
-    {
-        FacesContext fc = FacesContext.getCurrentInstance();
-        Map reqMap = fc.getExternalContext().getRequestMap();
-        reqMap.put("captureNavigation", Boolean.valueOf(state));
-    }
-
-    private static boolean isCaptureNavigation()
-    {
-        FacesContext fc = FacesContext.getCurrentInstance();
-        Map reqMap = fc.getExternalContext().getRequestMap();
-        return Boolean.TRUE.equals(reqMap.get("captureNavigation"));
-    }
-
-    private static void setNewViewInfo(String viewId, String viewUrl)
-    {
-        FacesContext fc = FacesContext.getCurrentInstance();
-        Map reqMap = fc.getExternalContext().getRequestMap();
-        reqMap.put("orchestraFlowId", viewId);
-        reqMap.put("orchestraFlowUrl", viewUrl);
+        return viewRoot;
     }
 }