You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2013/09/03 16:49:39 UTC

svn commit: r1519698 - in /myfaces/core/trunk: impl/src/main/java/org/apache/myfaces/application/ impl/src/main/java/org/apache/myfaces/flow/ impl/src/main/java/org/apache/myfaces/lifecycle/ impl/src/test/java/org/apache/myfaces/application/flow/ impl/...

Author: lu4242
Date: Tue Sep  3 14:49:39 2013
New Revision: 1519698

URL: http://svn.apache.org/r1519698
Log:
MYFACES-3765 Implement FlowHandler.clientWindowTransition(FacesContext context) and MYFACES-3764 Implement FlowHandler.getLastDisplayedViewId(FacesContext context) logic (fix algorithm)

Added:
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/FlowNavigationCase.java   (with props)
    myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/html/util/OutcomeTargetUtils.java   (with props)
Modified:
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/NavigationHandlerImpl.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/flow/FlowHandlerImpl.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/lifecycle/RestoreViewExecutor.java
    myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/application/flow/FlowMyFacesRequestTestCase.java
    myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/lifecycle/RestoreViewExecutorTest.java
    myfaces/core/trunk/impl/src/test/resources/org/apache/myfaces/application/flow/WEB-INF/flow1-flow.xml
    myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/JSFAttr.java
    myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/html/HtmlRendererUtils.java

Added: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/FlowNavigationCase.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/FlowNavigationCase.java?rev=1519698&view=auto
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/FlowNavigationCase.java (added)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/FlowNavigationCase.java Tue Sep  3 14:49:39 2013
@@ -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.application;
+
+import javax.faces.application.NavigationCase;
+import javax.faces.application.NavigationCaseWrapper;
+
+/**
+ * Wrapper that helps overriding toFlowDocumentId and fromOutcome, to build
+ * correctly a navigation case that cause a flow action (enter into a flow
+ * or return from a flow).
+ * 
+ * The idea is if is necessary to enter into a flow set fromOutcome as the
+ * flow id and toFlowDocumentId as the flow document id. If it is a return,
+ * set fromOutcome as the return node and toFlowDocumentId as FlowHandler.NULL_FLOW
+ * 
+ * @author Leonardo Uribe
+ */
+public class FlowNavigationCase extends NavigationCaseWrapper
+{
+    
+    private NavigationCase _delegate;
+    private String _fromOutcome;
+    private String _toFlowDocumentId;
+
+    public FlowNavigationCase(NavigationCase delegate, String fromOutcome, String toFlowDocumentId)
+    {
+        this._delegate = delegate;
+        this._fromOutcome = fromOutcome;
+        this._toFlowDocumentId = toFlowDocumentId;
+    }
+
+    @Override
+    public NavigationCase getWrapped()
+    {
+        return _delegate;
+    }
+    
+    @Override
+    public String getFromOutcome()
+    {
+        return _fromOutcome;
+    }
+    
+    @Override
+    public String getToFlowDocumentId()
+    {
+        return _toFlowDocumentId;
+    }
+}

Propchange: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/FlowNavigationCase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/NavigationHandlerImpl.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/NavigationHandlerImpl.java?rev=1519698&r1=1519697&r2=1519698&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/NavigationHandlerImpl.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/NavigationHandlerImpl.java Tue Sep  3 14:49:39 2013
@@ -216,7 +216,7 @@ public class NavigationHandlerImpl
                 ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
                 //create new view
                 String newViewId = navigationCase.getToViewId(facesContext);
-                String lastDisplayedViewId = null;
+
                 // JSF 2.0 the javadoc of handleNavigation() says something like this 
                 // "...If the view has changed after an application action, call
                 // PartialViewContext.setRenderAll(true)...". The effect is that ajax requests
@@ -233,8 +233,6 @@ public class NavigationHandlerImpl
 
                 if (facesContext.getViewRoot() != null)
                 {
-                    lastDisplayedViewId = facesContext.getViewRoot().getViewId();
-                    
                     if (facesContext.getViewRoot().getAttributes().containsKey("oam.CALL_PRE_DISPOSE_VIEW"))
                     {
                         facesContext.getAttributes().put(SKIP_ITERATION_HINT, Boolean.TRUE);
@@ -257,9 +255,15 @@ public class NavigationHandlerImpl
                         for (int i = 0; i < navigationContext.getTargetFlows().size(); i++)
                         {
                             Flow targetFlow = navigationContext.getTargetFlows().get(i);
+                            // TODO: Here there is an inconsistency with the spec. The javadoc
+                            // for transition says "... toViewId - the viewId of the view 
+                            // being displayed as a result of this transition. This parameter 
+                            // makes it possible to implement getLastDisplayedViewId(
+                            // javax.faces.context.FacesContext).  ...", but JSF 2.2 section
+                            // 7.4.2.1 says "...  ..."
                             flowHandler.transition(facesContext, sourceFlow, targetFlow, 
                                 navigationContext.getFlowCallNodes().get(i), 
-                                lastDisplayedViewId);
+                                navigationContext.getNavigationCase().getToViewId(facesContext));
                             sourceFlow = targetFlow;
                         }
                     }
@@ -428,6 +432,8 @@ public class NavigationHandlerImpl
             FlowCallNode targetFlowCallNode = null;
 
             boolean startFlow = false;
+            String startFlowDocumentId = null;
+            String startFlowId = null;
             boolean checkFlowNode = false;
             if (currentFlow != null)
             {
@@ -469,6 +475,11 @@ public class NavigationHandlerImpl
                 {
                     if (startFlow)
                     {
+                        if (startFlowId == null)
+                        {
+                            startFlowDocumentId = targetFlow.getDefiningDocumentId();
+                            startFlowId = targetFlowCallNode == null ? targetFlow.getId() : targetFlowCallNode.getId();
+                        }
                         navigationContext.setSourceFlow(currentFlow);
                         navigationContext.addTargetFlow(targetFlow, targetFlowCallNode);
                         targetFlowCallNode = null;
@@ -531,28 +542,8 @@ public class NavigationHandlerImpl
                             {
                                 // "... If the node is a FlowCallNode, save it aside as facesFlowCallNode. ..."
                                 FlowCallNode flowCallNode = (FlowCallNode) flowNode;
-
-                                // " ... Let flowId be the value of its calledFlowId property and flowDocumentId 
-                                // be the value of its calledFlowDocumentId property. .."
-
-                                // " ... If no flowDocumentId exists for the node, let it be the string 
-                                // resulting from flowId + "/" + flowId + ".xhtml". ..." 
-                                // -=Leonardo Uribe=- flowDocumentId is inconsistent, waiting for answer of the EG,
-                                // for not let it be null.
-
-                                String calledFlowDocumentId = flowCallNode.getCalledFlowDocumentId(facesContext);
-                                if (calledFlowDocumentId == null)
-                                {
-                                    calledFlowDocumentId = currentFlow.getDefiningDocumentId();
-                                }
-                                targetFlow = flowHandler.getFlow(facesContext, 
-                                    calledFlowDocumentId, 
-                                    flowCallNode.getCalledFlowId(facesContext));
-                                if (targetFlow == null && !"".equals(calledFlowDocumentId))
-                                {
-                                    targetFlow = flowHandler.getFlow(facesContext, "", 
-                                        flowCallNode.getCalledFlowId(facesContext));
-                                }
+                                targetFlow = calculateFlowCallTargetFlow(facesContext, 
+                                    flowHandler, flowCallNode, currentFlow);
                                 if (targetFlow != null)
                                 {
                                     targetFlowCallNode = flowCallNode;
@@ -565,30 +556,14 @@ public class NavigationHandlerImpl
                                     // reference to the start node and execute this algorithm again, on that start node.
                                     complete = true;
                                 }
-                                //complete = true;
                             }
                             if (!complete && flowNode instanceof MethodCallNode)
                             {
                                 MethodCallNode methodCallNode = (MethodCallNode) flowNode;
-                                String vdlViewIdentifier = null;
-                                MethodExpression method = methodCallNode.getMethodExpression();
-                                if (method != null)
-                                {
-                                    Object value = invokeMethodCallNode(facesContext, methodCallNode);
-                                    if (value != null)
-                                    {
-                                        vdlViewIdentifier = value.toString();
-                                    }
-                                    else if (methodCallNode.getOutcome() != null)
-                                    {
-                                        vdlViewIdentifier = (String) methodCallNode.getOutcome().getValue(
-                                            facesContext.getELContext());
-                                    }
-                                }
+                                String vdlViewIdentifier = calculateVdlViewIdentifier(facesContext, methodCallNode);
                                 // note a vdlViewIdentifier could be a flow node too
                                 if (vdlViewIdentifier != null)
                                 {
-                                    //navigationCase = createNavigationCase(viewId, outcomeToGo, vdlViewIdentifier);
                                     outcomeToGo = vdlViewIdentifier;
                                     actionToGo = currentFlow.getId();
                                     continue;
@@ -609,14 +584,14 @@ public class NavigationHandlerImpl
                                     navigationContext.setSourceFlow(
                                         navigationContext.getCurrentFlow(facesContext));
                                 }
-                                String lastDisplayedViewId = navigationContext.getLastDisplayedViewId(facesContext, 
-                                            currentFlow);
-                                
                                 // This is the part when the pseudo "recursive call" is done. 
                                 navigationContext.popFlow(facesContext);
                                 currentFlow = navigationContext.getCurrentFlow(facesContext);
                                 navigationContext.addTargetFlow(currentFlow, null);
                                 outcomeToGo = fromOutcome;
+                                String lastDisplayedViewId = navigationContext.getLastDisplayedViewId(facesContext, 
+                                            currentFlow);
+                                
                                 // The part where FlowHandler.NULL_FLOW is passed as documentId causes the effect of
                                 // do not take into account the documentId of the returned flow in the command. In 
                                 // theory there is no Flow with defining documentId as FlowHandler.NULL_FLOW. It has 
@@ -626,6 +601,8 @@ public class NavigationHandlerImpl
                                         navigationContext, actionToGo, outcomeToGo, FlowHandler.NULL_FLOW);
                                 if (navigationCase != null)
                                 {
+                                    navigationCase = new FlowNavigationCase(navigationCase, 
+                                        flowNode.getId(), FlowHandler.NULL_FLOW);
                                     complete = true;
                                 }
                                 else
@@ -634,7 +611,7 @@ public class NavigationHandlerImpl
                                     if (lastDisplayedViewId != null)
                                     {
                                         navigationCase = createNavigationCase(
-                                            viewId, flowNode.getId(), lastDisplayedViewId);
+                                            viewId, flowNode.getId(), lastDisplayedViewId, FlowHandler.NULL_FLOW);
                                         complete = true;
                                     }
                                 }
@@ -692,6 +669,10 @@ public class NavigationHandlerImpl
                     navigationCase = getOutcomeNavigationCase (facesContext, actionToGo, outcomeToGo);
                 }
             }
+            if (startFlowId != null)
+            {
+                navigationCase = new FlowNavigationCase(navigationCase, startFlowId, startFlowDocumentId);
+            }
         }
         if (outcome != null && navigationCase == null)
         {
@@ -714,6 +695,54 @@ public class NavigationHandlerImpl
         // if navigationCase == null, will stay on current view
     }
     
+    private Flow calculateFlowCallTargetFlow(FacesContext facesContext, FlowHandler flowHandler,
+        FlowCallNode flowCallNode, Flow currentFlow)
+    {
+        Flow targetFlow = null;
+        // " ... Let flowId be the value of its calledFlowId property and flowDocumentId 
+        // be the value of its calledFlowDocumentId property. .."
+
+        // " ... If no flowDocumentId exists for the node, let it be the string 
+        // resulting from flowId + "/" + flowId + ".xhtml". ..." 
+        // -=Leonardo Uribe=- flowDocumentId is inconsistent, waiting for answer of the EG,
+        // for not let it be null.
+
+        String calledFlowDocumentId = flowCallNode.getCalledFlowDocumentId(facesContext);
+        if (calledFlowDocumentId == null)
+        {
+            calledFlowDocumentId = currentFlow.getDefiningDocumentId();
+        }
+        targetFlow = flowHandler.getFlow(facesContext, 
+            calledFlowDocumentId, 
+            flowCallNode.getCalledFlowId(facesContext));
+        if (targetFlow == null && !"".equals(calledFlowDocumentId))
+        {
+            targetFlow = flowHandler.getFlow(facesContext, "", 
+                flowCallNode.getCalledFlowId(facesContext));
+        }
+        return targetFlow;
+    }
+    
+    private String calculateVdlViewIdentifier(FacesContext facesContext, MethodCallNode methodCallNode)
+    {
+        String vdlViewIdentifier = null;
+        MethodExpression method = methodCallNode.getMethodExpression();
+        if (method != null)
+        {
+            Object value = invokeMethodCallNode(facesContext, methodCallNode);
+            if (value != null)
+            {
+                vdlViewIdentifier = value.toString();
+            }
+            else if (methodCallNode.getOutcome() != null)
+            {
+                vdlViewIdentifier = (String) methodCallNode.getOutcome().getValue(
+                    facesContext.getELContext());
+            }
+        }
+        return vdlViewIdentifier;
+    }
+    
     private Object invokeMethodCallNode(FacesContext facesContext, MethodCallNode methodCallNode)
     {
         MethodExpression method = methodCallNode.getMethodExpression();
@@ -801,6 +830,13 @@ public class NavigationHandlerImpl
         return new NavigationCase(fromViewId, null, outcome, null, toViewId, null, false, false);
     }
     
+    private NavigationCase createNavigationCase(String fromViewId, String outcome, 
+        String toViewId, String toFlowDocumentId)
+    {
+        return new NavigationCase(fromViewId, null, outcome, null, toViewId, 
+            toFlowDocumentId, null, false, false);
+    }
+    
     /**
      * Performs the algorithm specified in 7.4.2 for situations where no navigation cases are defined and instead
      * the navigation case is to be determined from the outcome.
@@ -1386,6 +1422,7 @@ public class NavigationHandlerImpl
         private List<FlowCallNode> targetFlowCallNodes;
         private List<Flow> currentFlows;
         private Map<Flow, String> lastDisplayedViewIdMap;
+        private String rootLastDisplayedViewId;
 
         public NavigationContext()
         {
@@ -1461,7 +1498,9 @@ public class NavigationHandlerImpl
                         flowHandler.pushReturnMode(facesContext);
                         curFlow = flowHandler.getCurrentFlow(facesContext);
                     }
-                
+
+                    rootLastDisplayedViewId = flowHandler.getLastDisplayedViewId(facesContext);
+
                     for (int i = 0; i < currentFlows.size(); i++)
                     {
                         flowHandler.popReturnMode(facesContext);
@@ -1497,6 +1536,10 @@ public class NavigationHandlerImpl
         
         public String getLastDisplayedViewId(FacesContext facesContext, Flow flow)
         {
+            if (flow == null)
+            {
+                return rootLastDisplayedViewId;
+            }
             if (lastDisplayedViewIdMap != null)
             {
                 return lastDisplayedViewIdMap.get(flow);

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/flow/FlowHandlerImpl.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/flow/FlowHandlerImpl.java?rev=1519698&r1=1519697&r2=1519698&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/flow/FlowHandlerImpl.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/flow/FlowHandlerImpl.java Tue Sep  3 14:49:39 2013
@@ -24,13 +24,16 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import javax.faces.application.ConfigurableNavigationHandler;
+import javax.faces.application.NavigationCase;
 import javax.faces.application.NavigationHandler;
 import javax.faces.application.NavigationHandlerWrapper;
 import javax.faces.context.FacesContext;
 import javax.faces.flow.Flow;
 import javax.faces.flow.FlowCallNode;
 import javax.faces.flow.FlowHandler;
+import javax.faces.flow.FlowNode;
 import javax.faces.flow.Parameter;
+import javax.faces.flow.ReturnNode;
 import javax.faces.lifecycle.ClientWindow;
 import org.apache.myfaces.spi.FacesFlowProvider;
 import org.apache.myfaces.spi.FacesFlowProviderFactory;
@@ -43,6 +46,7 @@ import org.apache.myfaces.spi.FacesFlowP
 public class FlowHandlerImpl extends FlowHandler
 {
     private final static String CURRENT_FLOW_STACK = "oam.flow.STACK.";
+    private final static String ROOT_LAST_VIEW_ID = "oam.flow.ROOT_LAST_VIEW_ID.";
     
     private final static String RETURN_MODE = "oam.flow.RETURN_MODE";
     private final static String FLOW_RETURN_STACK = "oam.flow.RETURN_STACK.";
@@ -215,7 +219,7 @@ public class FlowHandlerImpl extends Flo
                     _FlowContextualInfo fci = currentFlowStack.get(i);
                     FlowReference fr = fci.getFlowReference();
                     doBeforeExitFlow(context, getFlow(context, fr.getDocumentId(), fr.getId()));
-                    currentFlowStack.remove(i);
+                    popFlowReference(context, clientWindow, currentFlowStack, i);
                 }
             }            
         }
@@ -245,7 +249,7 @@ public class FlowHandlerImpl extends Flo
                         _FlowContextualInfo fci = currentFlowStack.get(i);
                         FlowReference fr = fci.getFlowReference();
                         doBeforeExitFlow(context, getFlow(context, fr.getDocumentId(), fr.getId()));
-                        currentFlowStack.remove(i);
+                        popFlowReference(context, clientWindow, currentFlowStack, i);
                     }
                 }
                 else
@@ -271,7 +275,7 @@ public class FlowHandlerImpl extends Flo
                             _FlowContextualInfo fci = currentFlowStack.get(i);
                             FlowReference fr = fci.getFlowReference();
                             doBeforeExitFlow(context, getFlow(context, fr.getDocumentId(), fr.getId()));
-                            currentFlowStack.remove(i);
+                            popFlowReference(context, clientWindow, currentFlowStack, i);
                         }
                         
                         Map<String, Object> outboundParameters = doBeforeEnterFlow(context,
@@ -405,9 +409,241 @@ public class FlowHandlerImpl extends Flo
     @Override
     public void clientWindowTransition(FacesContext context)
     {
+        String flowDocumentIdRequestParam = (String) context.getExternalContext().
+            getRequestParameterMap().get(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME);
+        
+        if (flowDocumentIdRequestParam != null)
+        {
+            String flowIdRequestParam = (String) context.getExternalContext().
+                getRequestParameterMap().get(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME);
+            
+            if (flowIdRequestParam == null)
+            {
+                // If we don't have an fromOutcome, it is not possible to calculate the transitions
+                // involved.
+                return;
+            }
+            
+            FlowHandler flowHandler = context.getApplication().getFlowHandler();
+            ConfigurableNavigationHandler nh = 
+                (ConfigurableNavigationHandler) context.getApplication().getNavigationHandler();
+            
+            if (FlowHandler.NULL_FLOW.equals(flowDocumentIdRequestParam))
+            {
+                // It is a return node. The trick here is we need to calculate
+                // where the flow should return, because that information was not passed
+                // in the parameters of the link. 
+                String toFlowDocumentId = FlowHandler.NULL_FLOW;
+                String targetFlowId = null;
+                String fromOutcome = flowIdRequestParam;
+                
+                boolean failed = false;
+                int i = 0;
+                while (FlowHandler.NULL_FLOW.equals(toFlowDocumentId) && !failed)
+                {
+                    Flow currentFlow = flowHandler.getCurrentFlow(context);
+                    if (currentFlow == null)
+                    {
+                        failed = true;
+                        break;
+                    }
+                    String currentLastDisplayedViewId = flowHandler.getLastDisplayedViewId(context);
+                    FlowNode node = currentFlow.getNode(fromOutcome);
+                    if (node instanceof ReturnNode)
+                    {
+                        // Get the navigation case using the outcome
+                        flowHandler.pushReturnMode(context);
+                        currentFlow = flowHandler.getCurrentFlow(context);
+                        i++;
+                        
+                        NavigationCase navCase = nh.getNavigationCase(context, null, 
+                            ((ReturnNode) node).getFromOutcome(context), FlowHandler.NULL_FLOW);
+
+                        if (navCase == null)
+                        {
+                            if (currentLastDisplayedViewId != null)
+                            {
+                                if (currentFlow != null)
+                                {
+                                    toFlowDocumentId = currentFlow.getDefiningDocumentId();
+                                    targetFlowId = currentFlow.getId();
+                                }
+                                else
+                                {
+                                    // No active flow
+                                    toFlowDocumentId = null;
+                                }
+                            }
+                            else
+                            {
+                                // Invalid state because no navCase and 
+                                // no saved lastDisplayedViewId into session
+                                failed = true;
+                            }
+                        }
+                        else
+                        {
+                            if (FlowHandler.NULL_FLOW.equals(navCase.getToFlowDocumentId()))
+                            {
+                                fromOutcome = navCase.getFromOutcome();
+                            }
+                            else
+                            {
+                                // The absence of FlowHandler.NULL_FLOW means the return went somewhere else.
+                                if (currentFlow != null)
+                                {
+                                    toFlowDocumentId = currentFlow.getDefiningDocumentId();
+                                    targetFlowId = currentFlow.getId();
+                                }
+                                else
+                                {
+                                    // No active flow
+                                    toFlowDocumentId = null;
+                                }
+                            }
+                        }
+                    }
+                    else
+                    {
+                        failed = true;
+                    }
+                }
+                for (int j = 0; j<i; j++)
+                {
+                    flowHandler.popReturnMode(context);
+                }
+                if (failed)
+                {
+                    // Do nothing.
+                }
+                else 
+                {
+                    Flow targetFlow = targetFlowId == null ? null : 
+                        getFlow(context, toFlowDocumentId, targetFlowId);
+                    //Call transitions.
+                    flowHandler.transition(context, 
+                        flowHandler.getCurrentFlow(context),
+                        targetFlow, null, context.getViewRoot().getViewId());
+                }
+            }
+            else
+            {
+                // This transition is for start a new flow. In this case 
+                // FlowHandler.FLOW_ID_REQUEST_PARAM_NAME could be the flow name to enter
+                // or the flow call node to activate.
+                Flow currentFlow = flowHandler.getCurrentFlow(context);
+                FlowNode node = currentFlow != null ? currentFlow.getNode(flowIdRequestParam) : null;
+                Flow targetFlow = null;
+                FlowCallNode outboundCallNode = null;
+                if (node != null && node instanceof FlowCallNode)
+                {
+                    outboundCallNode = (FlowCallNode) node;
+                    
+                    String calledFlowDocumentId = outboundCallNode.getCalledFlowDocumentId(context);
+                    if (calledFlowDocumentId == null)
+                    {
+                        calledFlowDocumentId = currentFlow.getDefiningDocumentId();
+                    }
+                    targetFlow = flowHandler.getFlow(context, 
+                        calledFlowDocumentId, 
+                        outboundCallNode.getCalledFlowId(context));
+                    if (targetFlow == null && !"".equals(calledFlowDocumentId))
+                    {
+                        targetFlow = flowHandler.getFlow(context, "", 
+                            outboundCallNode.getCalledFlowId(context));
+                    }
+                    
+                    //targetFlow = flowHandler.getFlow(context, 
+                    //    outboundCallNode.getCalledFlowDocumentId(context), 
+                    //    outboundCallNode.getCalledFlowId(context));
+                }
+                else
+                {
+                    targetFlow = flowHandler.getFlow(context, flowDocumentIdRequestParam, flowIdRequestParam);
+                }
+                
+                if (targetFlow != null)
+                {
+                    // Invoke transition
+                    flowHandler.transition(context, 
+                        currentFlow, targetFlow, outboundCallNode, context.getViewRoot().getViewId());
+
+                    // TODO: Handle 2 or more flow start.
+                    boolean failed = false;
+                    
+                    String startNodeId = targetFlow.getStartNodeId();
+                    while (startNodeId != null && !failed)
+                    {
+                        NavigationCase navCase = nh.getNavigationCase(context, null, 
+                                    startNodeId, targetFlow.getDefiningDocumentId());
+                        
+                        if (navCase != null && navCase.getToFlowDocumentId() != null)
+                        {
+                            currentFlow = flowHandler.getCurrentFlow(context);
+                            node = currentFlow.getNode(navCase.getFromOutcome());
+                            if (node != null && node instanceof FlowCallNode)
+                            {
+                                outboundCallNode = (FlowCallNode) node;
+                                
+                                String calledFlowDocumentId = outboundCallNode.getCalledFlowDocumentId(context);
+                                if (calledFlowDocumentId == null)
+                                {
+                                    calledFlowDocumentId = currentFlow.getDefiningDocumentId();
+                                }
+                                targetFlow = flowHandler.getFlow(context, 
+                                    calledFlowDocumentId, 
+                                    outboundCallNode.getCalledFlowId(context));
+                                if (targetFlow == null && !"".equals(calledFlowDocumentId))
+                                {
+                                    targetFlow = flowHandler.getFlow(context, "", 
+                                        outboundCallNode.getCalledFlowId(context));
+                                }
+
+                                //targetFlow = flowHandler.getFlow(context, 
+                                //    outboundCallNode.getCalledFlowDocumentId(context), 
+                                //    outboundCallNode.getCalledFlowId(context));
+                            }
+                            else
+                            {
+                                String calledFlowDocumentId = navCase.getToFlowDocumentId();
+                                if (calledFlowDocumentId == null)
+                                {
+                                    calledFlowDocumentId = currentFlow.getDefiningDocumentId();
+                                }
+                                targetFlow = flowHandler.getFlow(context, 
+                                    calledFlowDocumentId, 
+                                    navCase.getFromOutcome());
+                                if (targetFlow == null && !"".equals(calledFlowDocumentId))
+                                {
+                                    targetFlow = flowHandler.getFlow(context, "", 
+                                        navCase.getFromOutcome());
+                                }
+                                //targetFlow = flowHandler.getFlow(context, 
+                                //    , navCase.getFromOutcome());
+                            }
+                            if (targetFlow != null)
+                            {
+                                flowHandler.transition(context, 
+                                    currentFlow, targetFlow, outboundCallNode, context.getViewRoot().getViewId());
+                                startNodeId = targetFlow.getStartNodeId();
+                            }
+                            else
+                            {
+                                startNodeId = null;
+                            }
+                        }
+                        else
+                        {
+                            startNodeId = null;
+                        }
+                    }
+                }
+                
+            }
+        }
         // throw new UnsupportedOperationException("Not supported yet.");
     }
-
+    
     private void checkNull(final Object o, final String param)
     {
         if (o == null)
@@ -457,7 +693,7 @@ public class FlowHandlerImpl extends Flo
     }
     
     private void pushFlowReference(FacesContext context, ClientWindow clientWindow, FlowReference flowReference,
-        String lastDisplayedViewId)
+        String toViewId)
     {
         Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
         String currentFlowMapKey = CURRENT_FLOW_STACK + clientWindow.getId();
@@ -467,7 +703,29 @@ public class FlowHandlerImpl extends Flo
             currentFlowStack = new ArrayList<_FlowContextualInfo>(4);
             sessionMap.put(currentFlowMapKey, currentFlowStack);
         }
-        currentFlowStack.add(new _FlowContextualInfo(flowReference, lastDisplayedViewId));
+        if (!currentFlowStack.isEmpty())
+        {
+            currentFlowStack.get(currentFlowStack.size()-1).setLastDisplayedViewId(context.getViewRoot().getViewId());
+        }
+        else
+        {
+            //Save root lastDisplayedViewId
+            context.getExternalContext().getSessionMap().put(ROOT_LAST_VIEW_ID + clientWindow.getId(), 
+                context.getViewRoot().getViewId());
+        }
+        currentFlowStack.add(new _FlowContextualInfo(flowReference, toViewId));
+    }
+    
+    private void popFlowReference(FacesContext context, ClientWindow clientWindow,
+        List<_FlowContextualInfo> currentFlowStack, int i)
+    {
+        currentFlowStack.remove(i);
+        if (currentFlowStack.isEmpty())
+        {
+            // Remove it from session but keep it in request scope.
+            context.getAttributes().put(ROOT_LAST_VIEW_ID, 
+                context.getExternalContext().getSessionMap().remove(ROOT_LAST_VIEW_ID + clientWindow.getId()));
+        }
     }
     
     private List<_FlowContextualInfo> getCurrentFlowStack(FacesContext context, ClientWindow clientWindow)
@@ -495,7 +753,13 @@ public class FlowHandlerImpl extends Flo
         _FlowContextualInfo info = getCurrentFlowReference(context, clientWindow);
         if (info == null)
         {
-            return null;
+            String lastDisplayedViewId = (String) context.getAttributes().get(ROOT_LAST_VIEW_ID);
+            if (lastDisplayedViewId == null)
+            {
+                lastDisplayedViewId = (String) context.getExternalContext().getSessionMap().
+                    get(ROOT_LAST_VIEW_ID + clientWindow.getId());
+            }
+            return lastDisplayedViewId;
         }
         return info.getLastDisplayedViewId();
     }

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/lifecycle/RestoreViewExecutor.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/lifecycle/RestoreViewExecutor.java?rev=1519698&r1=1519697&r2=1519698&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/lifecycle/RestoreViewExecutor.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/lifecycle/RestoreViewExecutor.java Tue Sep  3 14:49:39 2013
@@ -35,6 +35,7 @@ import javax.faces.context.FacesContext;
 import javax.faces.event.PhaseEvent;
 import javax.faces.event.PhaseId;
 import javax.faces.event.PostAddToViewEvent;
+import javax.faces.flow.FlowHandler;
 import javax.faces.lifecycle.Lifecycle;
 import javax.faces.lifecycle.LifecycleFactory;
 import javax.faces.view.ViewDeclarationLanguage;
@@ -226,6 +227,12 @@ class RestoreViewExecutor extends PhaseE
             // Store the new UIViewRoot instance in the FacesContext.
             facesContext.setViewRoot(viewRoot);
             
+            FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
+            if (flowHandler != null)
+            {
+                flowHandler.clientWindowTransition(facesContext);
+            }
+
             // Publish an AfterAddToParent event with the created UIViewRoot as the event source.
             application.publishEvent(facesContext, PostAddToViewEvent.class, viewRoot);
         }

Modified: myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/application/flow/FlowMyFacesRequestTestCase.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/application/flow/FlowMyFacesRequestTestCase.java?rev=1519698&r1=1519697&r2=1519698&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/application/flow/FlowMyFacesRequestTestCase.java (original)
+++ myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/application/flow/FlowMyFacesRequestTestCase.java Tue Sep  3 14:49:39 2013
@@ -18,11 +18,14 @@
  */
 package org.apache.myfaces.application.flow;
 
+import javax.el.ExpressionFactory;
 import javax.faces.application.ConfigurableNavigationHandler;
 import javax.faces.application.NavigationCase;
 import javax.faces.application.StateManager;
 import javax.faces.component.UICommand;
 import javax.faces.flow.Flow;
+import javax.faces.flow.FlowHandler;
+import javax.faces.render.ResponseStateManager;
 import org.apache.myfaces.mc.test.core.AbstractMyFacesRequestTestCase;
 import org.apache.myfaces.shared.config.MyfacesConfig;
 import org.junit.Test;
@@ -256,4 +259,225 @@ public class FlowMyFacesRequestTestCase 
         
     }
 
+    @Test
+    public void testFlow1_4() throws Exception
+    {
+        setupRequest("/flow1_2.xhtml");
+        processLifecycleExecute();
+        
+        ConfigurableNavigationHandler handler = (ConfigurableNavigationHandler) facesContext.getApplication().getNavigationHandler();
+        
+        NavigationCase navCase = handler.getNavigationCase(facesContext, null, "flow1");
+        
+        Assert.assertNotNull(navCase);
+        
+        // Check begin view node
+        Assert.assertEquals("/flow1/begin.xhtml", navCase.getToViewId(facesContext));
+        
+        processRender();
+       
+        //Enter flow 1
+        UICommand button = (UICommand) facesContext.getViewRoot().findComponent("mainForm:startFlow1");
+        submit(button);
+        
+        processLifecycleExecute();
+        
+        Flow currentFlow = facesContext.getApplication().getFlowHandler().getCurrentFlow(facesContext);
+        Assert.assertNotNull(currentFlow);
+        Assert.assertEquals("flow1", currentFlow.getId());
+        
+        facesContext.getApplication().getFlowHandler().getCurrentFlowScope().put("flow1","value1");
+        
+        processRender();
+        
+        UICommand button2 = (UICommand) facesContext.getViewRoot().findComponent("mainForm:call_flow2");
+        submit(button2);
+        
+        processLifecycleExecute();
+        
+        currentFlow = facesContext.getApplication().getFlowHandler().getCurrentFlow(facesContext);
+        Assert.assertNotNull(currentFlow);
+        Assert.assertEquals("flow2", currentFlow.getId());
+        Assert.assertFalse(facesContext.getApplication().getFlowHandler().getCurrentFlowScope().containsKey("flow1"));
+        facesContext.getApplication().getFlowHandler().getCurrentFlowScope().put("flow2","value2");
+        
+        processRender();
+        
+        //Check current view is the begin of flow2
+        Assert.assertEquals("/flow2/begin.xhtml", facesContext.getViewRoot().getViewId());
+        
+        UICommand button3 = (UICommand) facesContext.getViewRoot().findComponent("mainForm:content");
+        submit(button3);
+        processLifecycleExecute();
+        processRender();
+        
+        currentFlow = facesContext.getApplication().getFlowHandler().getCurrentFlow(facesContext);
+        Assert.assertNotNull(currentFlow);
+        Assert.assertEquals("flow2", currentFlow.getId());
+
+        NavigationCase endCase = handler.getNavigationCase(facesContext, null, "back");
+        Assert.assertNotNull(endCase);
+        String toViewId = endCase.getToViewId(facesContext);
+        String fromOutcome = endCase.getFromOutcome();
+        String clientWindowId = facesContext.getExternalContext().getClientWindow().getId();
+        
+        tearDownRequest();
+        setupRequest(toViewId);
+        request.addParameter(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME, FlowHandler.NULL_FLOW);
+        request.addParameter(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME, fromOutcome);
+        request.addParameter(ResponseStateManager.CLIENT_WINDOW_URL_PARAM, clientWindowId);
+        
+        processLifecycleExecute();
+        
+        // Check it should go back to flow1 
+        currentFlow = facesContext.getApplication().getFlowHandler().getCurrentFlow(facesContext);
+        Assert.assertNotNull(currentFlow);
+        Assert.assertEquals("flow1", currentFlow.getId());
+        Assert.assertTrue(facesContext.getApplication().getFlowHandler().getCurrentFlowScope().containsKey("flow1"));
+        // Check lastDisplayedViewId
+        //Assert.assertEquals("/flow1/begin.xhtml", facesContext.getViewRoot().getViewId());
+        
+        processRender();
+        
+        endCase = handler.getNavigationCase(facesContext, null, "back");
+        Assert.assertNotNull(endCase);
+
+        toViewId = endCase.getToViewId(facesContext);
+        fromOutcome = endCase.getFromOutcome();
+        clientWindowId = facesContext.getExternalContext().getClientWindow().getId();
+        
+        tearDownRequest();
+        setupRequest(toViewId);
+        request.addParameter(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME, FlowHandler.NULL_FLOW);
+        request.addParameter(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME, fromOutcome);
+        request.addParameter(ResponseStateManager.CLIENT_WINDOW_URL_PARAM, clientWindowId);
+        
+        processLifecycleExecute();
+        
+        currentFlow = facesContext.getApplication().getFlowHandler().getCurrentFlow(facesContext);
+        Assert.assertNull(currentFlow);
+        Assert.assertEquals("/flow1_2.xhtml", facesContext.getViewRoot().getViewId());
+    }     
+    
+    @Test
+    public void testFlow1_5() throws Exception
+    {
+        setupRequest("/flow1_2.xhtml");
+        processLifecycleExecute();
+        
+        ConfigurableNavigationHandler handler = (ConfigurableNavigationHandler) facesContext.getApplication().getNavigationHandler();
+        
+        NavigationCase navCase = handler.getNavigationCase(facesContext, null, "flow3");
+        
+        Assert.assertNotNull(navCase); 
+        
+        // Check begin view node
+        Assert.assertEquals("/flow1/begin.xhtml", navCase.getToViewId(facesContext));
+        
+        processRender();
+        String clientWindowId = facesContext.getExternalContext().getClientWindow().getId();
+        
+        setupRequest(navCase.getToViewId(facesContext));
+        request.addParameter(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME, navCase.getToFlowDocumentId());
+        request.addParameter(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME, navCase.getFromOutcome());
+        request.addParameter(ResponseStateManager.CLIENT_WINDOW_URL_PARAM, clientWindowId);
+       
+        //Enter flow 1
+        //UICommand button = (UICommand) facesContext.getViewRoot().findComponent("mainForm:startFlow1");
+        //submit(button);
+        
+        processLifecycleExecute();
+        
+        Flow currentFlow = facesContext.getApplication().getFlowHandler().getCurrentFlow(facesContext);
+        Assert.assertNotNull(currentFlow);
+        Assert.assertEquals("flow1", currentFlow.getId());
+        
+        facesContext.getApplication().getFlowHandler().getCurrentFlowScope().put("flow1","value1");
+        
+        processRender();
+        
+        //UICommand button2 = (UICommand) facesContext.getViewRoot().findComponent("mainForm:call_flow2");
+        //submit(button2);
+        navCase = handler.getNavigationCase(facesContext, null, "call_flow2");
+        
+        clientWindowId = facesContext.getExternalContext().getClientWindow().getId();
+        
+        setupRequest(navCase.getToViewId(facesContext));
+        request.addParameter(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME, navCase.getToFlowDocumentId());
+        request.addParameter(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME, navCase.getFromOutcome());
+        request.addParameter(ResponseStateManager.CLIENT_WINDOW_URL_PARAM, clientWindowId);
+        
+        processLifecycleExecute();
+        
+        currentFlow = facesContext.getApplication().getFlowHandler().getCurrentFlow(facesContext);
+        Assert.assertNotNull(currentFlow);
+        Assert.assertEquals("flow2", currentFlow.getId());
+        Assert.assertFalse(facesContext.getApplication().getFlowHandler().getCurrentFlowScope().containsKey("flow1"));
+        facesContext.getApplication().getFlowHandler().getCurrentFlowScope().put("flow2","value2");
+        
+        processRender();
+        
+        //Check current view is the begin of flow2
+        Assert.assertEquals("/flow2/begin.xhtml", facesContext.getViewRoot().getViewId());
+        
+        UICommand button3 = (UICommand) facesContext.getViewRoot().findComponent("mainForm:content");
+        submit(button3);
+        processLifecycleExecute();
+        processRender();
+        
+        currentFlow = facesContext.getApplication().getFlowHandler().getCurrentFlow(facesContext);
+        Assert.assertNotNull(currentFlow);
+        Assert.assertEquals("flow2", currentFlow.getId());
+
+        NavigationCase endCase = handler.getNavigationCase(facesContext, null, "back");
+        Assert.assertNotNull(endCase);
+        String toViewId = endCase.getToViewId(facesContext);
+        String fromOutcome = endCase.getFromOutcome();
+        clientWindowId = facesContext.getExternalContext().getClientWindow().getId();
+        
+        tearDownRequest();
+        setupRequest(toViewId);
+        request.addParameter(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME, FlowHandler.NULL_FLOW);
+        request.addParameter(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME, fromOutcome);
+        request.addParameter(ResponseStateManager.CLIENT_WINDOW_URL_PARAM, clientWindowId);
+        
+        processLifecycleExecute();
+        
+        // Check it should go back to flow1 
+        currentFlow = facesContext.getApplication().getFlowHandler().getCurrentFlow(facesContext);
+        Assert.assertNotNull(currentFlow);
+        Assert.assertEquals("flow1", currentFlow.getId());
+        Assert.assertTrue(facesContext.getApplication().getFlowHandler().getCurrentFlowScope().containsKey("flow1"));
+        // Check lastDisplayedViewId (since it was GET, it should be the start viewId)
+        Assert.assertEquals("/flow2/begin.xhtml", facesContext.getViewRoot().getViewId());
+        
+        processRender();
+        
+        endCase = handler.getNavigationCase(facesContext, null, "switchBack");
+        Assert.assertNotNull(endCase);
+
+        toViewId = endCase.getToViewId(facesContext);
+        // Check if the dynamic outcome return hack has been correctly resolved. 
+        Assert.assertEquals(toViewId, "/flow3/content.xhtml");
+        fromOutcome = endCase.getFromOutcome();
+        clientWindowId = facesContext.getExternalContext().getClientWindow().getId();
+        
+        tearDownRequest();
+        setupRequest(toViewId);
+        request.addParameter(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME, FlowHandler.NULL_FLOW);
+        request.addParameter(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME, fromOutcome);
+        request.addParameter(ResponseStateManager.CLIENT_WINDOW_URL_PARAM, clientWindowId);
+        
+        processLifecycleExecute();
+        
+        currentFlow = facesContext.getApplication().getFlowHandler().getCurrentFlow(facesContext);
+        Assert.assertNotNull(currentFlow);
+        
+        //Assert.assertEquals("/flow1_2.xhtml", facesContext.getViewRoot().getViewId());
+    }    
+    
+    protected ExpressionFactory createExpressionFactory()
+    {
+        return new org.apache.el.ExpressionFactoryImpl();
+    }
 }

Modified: myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/lifecycle/RestoreViewExecutorTest.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/lifecycle/RestoreViewExecutorTest.java?rev=1519698&r1=1519697&r2=1519698&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/lifecycle/RestoreViewExecutorTest.java (original)
+++ myfaces/core/trunk/impl/src/test/java/org/apache/myfaces/lifecycle/RestoreViewExecutorTest.java Tue Sep  3 14:49:39 2013
@@ -104,7 +104,7 @@ public class RestoreViewExecutorTest ext
         expect(vdl.getViewMetadata(same(_facesContext), eq("calculatedViewId")))
             .andReturn(null);
         expect(_viewHandler.createView(same(_facesContext), eq("calculatedViewId"))).andReturn(viewRoot);
-
+        expect(_application.getFlowHandler()).andReturn(null);
         _application.publishEvent(same(_facesContext), same(PostAddToViewEvent.class), same(viewRoot));
         _facesContext.setViewRoot(same(viewRoot));
         expect(_facesContext.getViewRoot()).andReturn(viewRoot);

Modified: myfaces/core/trunk/impl/src/test/resources/org/apache/myfaces/application/flow/WEB-INF/flow1-flow.xml
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/test/resources/org/apache/myfaces/application/flow/WEB-INF/flow1-flow.xml?rev=1519698&r1=1519697&r2=1519698&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/test/resources/org/apache/myfaces/application/flow/WEB-INF/flow1-flow.xml (original)
+++ myfaces/core/trunk/impl/src/test/resources/org/apache/myfaces/application/flow/WEB-INF/flow1-flow.xml Tue Sep  3 14:49:39 2013
@@ -23,8 +23,18 @@
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_2.xsd">
 
+    <name>flow_def_1</name>
+    
     <flow-definition id="flow1">
         <start-node>begin</start-node>
+        
+        <inbound-parameter>
+            <!-- The idea is when entering a flow pass the return outcome in
+                 the flow call. This value will be saved in flowScope
+                 and used later to calculate the returning outcome -->
+            <name>backOutcome</name>
+            <value>#{flowScope.backOutcome}</value>
+        </inbound-parameter>
 
         <flow-return id="end">
             <from-outcome>exit</from-outcome>
@@ -36,6 +46,21 @@
             <from-outcome>back</from-outcome>
         </flow-return>
         
+        <!-- The idea is use a switch combo to get back, since
+             the from-outcome of the switch cannot receive
+             EL expressions -->
+        <switch id="switchBack">
+            <case>
+                <if>#{not empty flowScope.backOutcome}</if>
+                <from-outcome>backEL</from-outcome>
+            </case>
+            <default-outcome>back</default-outcome>
+        </switch>
+        
+        <flow-return id="backEL">
+            <from-outcome>#{flowScope.backOutcome}</from-outcome>
+        </flow-return>
+        
         <navigation-rule>
             <from-view-id>*</from-view-id>
             <navigation-case>
@@ -97,6 +122,41 @@
         </view>
     </flow-definition>
     
+    <!-- The idea of this flow is call a second flow, so as soon as
+    the algorithm try to enter flow3 it should enter flow1. -->
+    <flow-definition id="flow3">
+        <start-node>call_flow1</start-node>
+        
+        <flow-call id="call_flow1">
+            <flow-reference>
+                <flow-document-id>flow_def_1</flow-document-id>
+                <flow-id>flow1</flow-id>
+            </flow-reference>
+            <outbound-parameter>
+                <name>backOutcome</name>
+                <value>content</value>
+            </outbound-parameter>
+        </flow-call>
+        
+        <navigation-rule>
+            <from-view-id>*</from-view-id>
+            <navigation-case>
+                <from-action>flow1</from-action>
+                <from-outcome>exit</from-outcome>
+                <to-view-id>content</to-view-id>
+            </navigation-case>
+        </navigation-rule>
+        
+        <flow-return id="end">
+            <from-outcome>exit</from-outcome>
+        </flow-return>
+        
+        <view id="content">
+            <vdl-document>/flow3/content.xhtml</vdl-document>
+        </view>
+        
+    </flow-definition>
+    
     <navigation-rule>
         <from-view-id>*</from-view-id>
         <!-- End Flow 1 -->
@@ -105,6 +165,7 @@
             <from-outcome>exit</from-outcome>
             <to-view-id>/flow1_end.xhtml</to-view-id>
         </navigation-case>
+        
     </navigation-rule>
     
 </faces-config>

Modified: myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/JSFAttr.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/JSFAttr.java?rev=1519698&r1=1519697&r2=1519698&view=diff
==============================================================================
--- myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/JSFAttr.java (original)
+++ myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/JSFAttr.java Tue Sep  3 14:49:39 2013
@@ -18,6 +18,8 @@
  */
 package org.apache.myfaces.shared.renderkit;
 
+import javax.faces.event.ActionListener;
+
 
 /**
  * Constant declarations for JSF tags
@@ -166,4 +168,6 @@ public interface JSFAttr
     // HtmlSelectManyCheckbox attributes
     String LAYOUT_WIDTH_ATTR           = "layoutWidth";
 
+    //
+    String TO_FLOW_DOCUMENT_ID_ATTR    = ActionListener.TO_FLOW_DOCUMENT_ID_ATTR_NAME;
 }

Modified: myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/html/HtmlRendererUtils.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/html/HtmlRendererUtils.java?rev=1519698&r1=1519697&r2=1519698&view=diff
==============================================================================
--- myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/html/HtmlRendererUtils.java (original)
+++ myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/html/HtmlRendererUtils.java Tue Sep  3 14:49:39 2013
@@ -34,11 +34,6 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.faces.FacesException;
-import javax.faces.application.ConfigurableNavigationHandler;
-import javax.faces.application.NavigationCase;
-import javax.faces.application.NavigationHandler;
-import javax.faces.application.ProjectStage;
-import javax.faces.application.ViewHandler;
 import javax.faces.component.EditableValueHolder;
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIInput;
@@ -64,7 +59,6 @@ import javax.faces.convert.Converter;
 import javax.faces.model.SelectItem;
 import javax.faces.model.SelectItemGroup;
 
-import org.apache.myfaces.shared.application.NavigationUtils;
 import org.apache.myfaces.shared.component.DisplayValueOnlyCapable;
 import org.apache.myfaces.shared.component.EscapeCapable;
 import org.apache.myfaces.shared.config.MyfacesConfig;
@@ -74,6 +68,7 @@ import org.apache.myfaces.shared.renderk
 import org.apache.myfaces.shared.renderkit.html.util.FormInfo;
 import org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder;
 import org.apache.myfaces.shared.renderkit.html.util.JavascriptUtils;
+import org.apache.myfaces.shared.renderkit.html.util.OutcomeTargetUtils;
 
 /**
  * @author Manfred Geiler (latest modification by $Author$)
@@ -1635,101 +1630,7 @@ public final class HtmlRendererUtils
     public static String getOutcomeTargetHref(FacesContext facesContext,
             UIOutcomeTarget component) throws IOException
     {
-        String outcome = component.getOutcome();
-        outcome = (outcome == null) ? facesContext.getViewRoot().getViewId()
-                : outcome;
-        outcome = ((outcome == null) ? STR_EMPTY : outcome.trim());
-        // Get the correct URL for the outcome.
-        NavigationHandler nh = facesContext.getApplication().getNavigationHandler();
-        if (!(nh instanceof ConfigurableNavigationHandler))
-        {
-            throw new FacesException(
-                    "Navigation handler must be an instance of "
-                            + "ConfigurableNavigationHandler for using h:link or h:button");
-        }
-        ConfigurableNavigationHandler navigationHandler = (ConfigurableNavigationHandler) nh;
-        // fromAction is null because there is no action method that was called to get the outcome
-        NavigationCase navigationCase = navigationHandler.getNavigationCase(
-                facesContext, null, outcome);
-        // when navigation case is null, force the link or button to be disabled and log a warning
-        if (navigationCase == null)
-        {
-            // log a warning
-            log.warning("Could not determine NavigationCase for UIOutcomeTarget component "
-                    + RendererUtils.getPathToComponent(component));
-
-            return null;
-        }
-        Map<String, List<String>> parameters = null;
-        // handle URL parameters
-        if (component.getChildCount() > 0)
-        {
-            List<UIParameter> validParams = getValidUIParameterChildren(
-                    facesContext, component.getChildren(), true, false);
-            if (validParams.size() > 0)
-            {
-                parameters = new HashMap<String, List<String>>();
-            }
-            for (int i = 0, size = validParams.size(); i < size; i++)
-            {
-                UIParameter param = validParams.get(i);
-                String name = param.getName();
-                Object value = param.getValue();
-                if (parameters.containsKey(name))
-                {
-                    parameters.get(name).add(value.toString());
-                }
-                else
-                {
-                    List<String> list = new ArrayList<String>(1);
-                    list.add(value.toString());
-                    parameters.put(name, list);
-                }
-            }
-        }
-        // handle NavigationCase parameters
-        Map<String, List<String>> navigationCaseParams = 
-            NavigationUtils.getEvaluatedNavigationParameters(facesContext,
-                navigationCase.getParameters());
-        if (navigationCaseParams != null)
-        {
-            if (parameters == null)
-            {
-                parameters = new HashMap<String, List<String>>();
-            }
-            //parameters.putAll(navigationCaseParams);
-            for (Map.Entry<String, List<String>> entry : navigationCaseParams
-                    .entrySet())
-            {
-                if (!parameters.containsKey(entry.getKey()))
-                {
-                    parameters.put(entry.getKey(), entry.getValue());
-                }
-            }
-        }
-        if (parameters == null)
-        {
-            parameters = Collections.emptyMap();
-        }
-        // In theory the precedence order to deal with params is this:
-        // component parameters, navigation-case parameters, view parameters
-        // getBookmarkableURL deal with this details.
-        ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
-        String href = viewHandler.getBookmarkableURL(facesContext,
-                navigationCase.getToViewId(facesContext),
-                parameters, navigationCase.isIncludeViewParams() || component.isIncludeViewParams());
-        // handle fragment (viewId#fragment)
-        String fragment = (String) component.getAttributes().get("fragment");
-        if (fragment != null)
-        {
-            fragment = fragment.trim();
-
-            if (fragment.length() > 0)
-            {
-                href += "#" + fragment;
-            }
-        }
-        return href;
+        return OutcomeTargetUtils.getOutcomeTargetHref(facesContext, component);
     }
 
     private static final String HTML_CONTENT_TYPE = "text/html";
@@ -2296,55 +2197,8 @@ public final class HtmlRendererUtils
             FacesContext facesContext, List<UIComponent> children,
             boolean skipNullValue, boolean skipUnrendered, boolean skipNullName)
     {
-        List<UIParameter> params = null;
-        for (int i = 0, size = children.size(); i < size; i++)
-        {
-            UIComponent child = children.get(i);
-            if (child instanceof UIParameter)
-            {
-                UIParameter param = (UIParameter) child;
-                // check for the disable attribute (since 2.0)
-                // and the render attribute (only if skipUnrendered is true)
-                if (param.isDisable() || (skipUnrendered && !param.isRendered()))
-                {
-                    // ignore this UIParameter and continue
-                    continue;
-                }
-                // check the name
-                String name = param.getName();
-                if (skipNullName && (name == null || STR_EMPTY.equals(name)))
-                {
-                    // warn for a null-name
-                    log.log(Level.WARNING, "The UIParameter " + RendererUtils.getPathToComponent(param)
-                                    + " has a name of null or empty string and thus will not be added to the URL.");
-                    // and skip it
-                    continue;
-                }
-                // check the value
-                if (skipNullValue && param.getValue() == null)
-                {
-                    if (facesContext.isProjectStage(ProjectStage.Development))
-                    {
-                        // inform the user about the null value when in Development stage
-                        log.log(Level.INFO, "The UIParameter " + RendererUtils.getPathToComponent(param)
-                                        + " has a value of null and thus will not be added to the URL.");
-                    }
-                    // skip a null-value
-                    continue;
-                }
-                // add the param
-                if (params == null)
-                {
-                    params = new ArrayList<UIParameter>();
-                }
-                params.add(param);
-            }
-        }
-        if (params == null)
-        {
-            params = Collections.emptyList();
-        }
-        return params;
+        return OutcomeTargetUtils.getValidUIParameterChildren(
+            facesContext, children, skipNullValue, skipUnrendered, skipNullName);
     }
 
     /**

Added: myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/html/util/OutcomeTargetUtils.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/html/util/OutcomeTargetUtils.java?rev=1519698&view=auto
==============================================================================
--- myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/html/util/OutcomeTargetUtils.java (added)
+++ myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/html/util/OutcomeTargetUtils.java Tue Sep  3 14:49:39 2013
@@ -0,0 +1,285 @@
+/*
+ * 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.shared.renderkit.html.util;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.faces.FacesException;
+import javax.faces.application.ConfigurableNavigationHandler;
+import javax.faces.application.NavigationCase;
+import javax.faces.application.NavigationHandler;
+import javax.faces.application.ProjectStage;
+import javax.faces.application.ViewHandler;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIOutcomeTarget;
+import javax.faces.component.UIParameter;
+import javax.faces.context.FacesContext;
+import javax.faces.flow.FlowHandler;
+import org.apache.myfaces.shared.application.NavigationUtils;
+import org.apache.myfaces.shared.renderkit.JSFAttr;
+import org.apache.myfaces.shared.renderkit.RendererUtils;
+import org.apache.myfaces.shared.renderkit.html.HtmlRendererUtils;
+
+/**
+ * Utility methods for OutcomeTarget components.
+ *
+ * @author Leonardo Uribe
+ */
+public class OutcomeTargetUtils
+{
+    
+    private static final Logger log = Logger.getLogger(OutcomeTargetUtils.class
+            .getName());
+    
+    public static String getOutcomeTargetHref(FacesContext facesContext,
+            UIOutcomeTarget component) throws IOException
+    {
+        String outcome = component.getOutcome();
+        outcome = (outcome == null) ? facesContext.getViewRoot().getViewId()
+                : outcome;
+        outcome = ((outcome == null) ? HtmlRendererUtils.STR_EMPTY : outcome.trim());
+        // Get the correct URL for the outcome.
+        NavigationHandler nh = facesContext.getApplication().getNavigationHandler();
+        if (!(nh instanceof ConfigurableNavigationHandler))
+        {
+            throw new FacesException(
+                    "Navigation handler must be an instance of "
+                            + "ConfigurableNavigationHandler for using h:link or h:button");
+        }
+        ConfigurableNavigationHandler navigationHandler = (ConfigurableNavigationHandler) nh;
+        
+        // handle faces flow 
+        // 1. check to-flow-document-id
+        String toFlowDocumentId = (String) component.getAttributes().get(
+            JSFAttr.TO_FLOW_DOCUMENT_ID_ATTR);
+        
+        // fromAction is null because there is no action method that was called to get the outcome
+        NavigationCase navigationCase = null;
+        if (toFlowDocumentId == null)
+        {
+            navigationCase = navigationHandler.getNavigationCase(
+                facesContext, null, outcome);
+        }
+        else
+        {
+            navigationCase = navigationHandler.getNavigationCase(
+                facesContext, null, outcome, toFlowDocumentId);            
+        }
+        
+        // when navigation case is null, force the link or button to be disabled and log a warning
+        if (navigationCase == null)
+        {
+            // log a warning
+            log.warning("Could not determine NavigationCase for UIOutcomeTarget component "
+                    + RendererUtils.getPathToComponent(component));
+
+            return null;
+        }
+        Map<String, List<String>> parameters = null;
+        // handle URL parameters
+        if (component.getChildCount() > 0)
+        {
+            List<UIParameter> validParams = getValidUIParameterChildren(
+                    facesContext, component.getChildren(), true, false);
+            if (validParams.size() > 0)
+            {
+                parameters = new HashMap<String, List<String>>();
+            }
+            for (int i = 0, size = validParams.size(); i < size; i++)
+            {
+                UIParameter param = validParams.get(i);
+                String name = param.getName();
+                Object value = param.getValue();
+                if (parameters.containsKey(name))
+                {
+                    parameters.get(name).add(value.toString());
+                }
+                else
+                {
+                    List<String> list = new ArrayList<String>(1);
+                    list.add(value.toString());
+                    parameters.put(name, list);
+                }
+            }
+        }
+        
+        // From the navigation case, use getToFlowDocumentId() to identify when
+        // a navigation case is a flow call or a flow return.
+        if (navigationCase.getToFlowDocumentId() != null)
+        {
+            if (parameters == null)
+            {
+                parameters = new HashMap<String, List<String>>();
+            }
+            if (!parameters.containsKey(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME))
+            {
+                List<String> list = new ArrayList<String>(1);
+                list.add(navigationCase.getToFlowDocumentId());
+                parameters.put(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME, list);
+            }
+            if (!parameters.containsKey(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME))
+            {
+                List<String> list2 = new ArrayList<String>(1);
+                list2.add(navigationCase.getFromOutcome());
+                parameters.put(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME, list2);
+            }
+        }
+        
+        // handle NavigationCase parameters
+        Map<String, List<String>> navigationCaseParams = 
+            NavigationUtils.getEvaluatedNavigationParameters(facesContext,
+                navigationCase.getParameters());
+        if (navigationCaseParams != null)
+        {
+            if (parameters == null)
+            {
+                parameters = new HashMap<String, List<String>>();
+            }
+            //parameters.putAll(navigationCaseParams);
+            for (Map.Entry<String, List<String>> entry : navigationCaseParams
+                    .entrySet())
+            {
+                if (!parameters.containsKey(entry.getKey()))
+                {
+                    parameters.put(entry.getKey(), entry.getValue());
+                }
+            }
+        }
+        if (parameters == null)
+        {
+            parameters = Collections.emptyMap();
+        }
+        // In theory the precedence order to deal with params is this:
+        // component parameters, navigation-case parameters, view parameters
+        // getBookmarkableURL deal with this details.
+        ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
+        String href = viewHandler.getBookmarkableURL(facesContext,
+                navigationCase.getToViewId(facesContext),
+                parameters, navigationCase.isIncludeViewParams() || component.isIncludeViewParams());
+        // handle fragment (viewId#fragment)
+        String fragment = (String) component.getAttributes().get("fragment");
+        if (fragment != null)
+        {
+            fragment = fragment.trim();
+
+            if (fragment.length() > 0)
+            {
+                href += "#" + fragment;
+            }
+        }
+        return href;
+    }
+
+    /**
+     * Calls getValidUIParameterChildren(facesContext, children, skipNullValue, skipUnrendered, true);
+     *
+     * @param facesContext
+     * @param children
+     * @param skipNullValue
+     * @param skipUnrendered
+     * @return ArrayList size > 0 if any parameter found
+     */
+    public static List<UIParameter> getValidUIParameterChildren(
+            FacesContext facesContext, List<UIComponent> children,
+            boolean skipNullValue, boolean skipUnrendered)
+    {
+        return getValidUIParameterChildren(facesContext, children,
+                skipNullValue, skipUnrendered, true);
+    }
+    
+    
+    /**
+     * Returns a List of all valid UIParameter children from the given children.
+     * Valid means that the UIParameter is not disabled, its name is not null
+     * (if skipNullName is true), its value is not null (if skipNullValue is true)
+     * and it is rendered (if skipUnrendered is true). This method also creates a
+     * warning for every UIParameter with a null-name (again, if skipNullName is true)
+     * and, if ProjectStage is Development and skipNullValue is true, it informs the
+     * user about every null-value.
+     *
+     * @param facesContext
+     * @param children
+     * @param skipNullValue  should UIParameters with a null value be skipped
+     * @param skipUnrendered should UIParameters with isRendered() returning false be skipped
+     * @param skipNullName   should UIParameters with a null name be skipped
+     *                       (normally true, but in the case of h:outputFormat false)
+     * @return ArrayList size > 0 if any parameter found 
+     */
+    public static List<UIParameter> getValidUIParameterChildren(
+            FacesContext facesContext, List<UIComponent> children,
+            boolean skipNullValue, boolean skipUnrendered, boolean skipNullName)
+    {
+        List<UIParameter> params = null;
+        for (int i = 0, size = children.size(); i < size; i++)
+        {
+            UIComponent child = children.get(i);
+            if (child instanceof UIParameter)
+            {
+                UIParameter param = (UIParameter) child;
+                // check for the disable attribute (since 2.0)
+                // and the render attribute (only if skipUnrendered is true)
+                if (param.isDisable() || (skipUnrendered && !param.isRendered()))
+                {
+                    // ignore this UIParameter and continue
+                    continue;
+                }
+                // check the name
+                String name = param.getName();
+                if (skipNullName && (name == null || HtmlRendererUtils.STR_EMPTY.equals(name)))
+                {
+                    // warn for a null-name
+                    log.log(Level.WARNING, "The UIParameter " + RendererUtils.getPathToComponent(param)
+                                    + " has a name of null or empty string and thus will not be added to the URL.");
+                    // and skip it
+                    continue;
+                }
+                // check the value
+                if (skipNullValue && param.getValue() == null)
+                {
+                    if (facesContext.isProjectStage(ProjectStage.Development))
+                    {
+                        // inform the user about the null value when in Development stage
+                        log.log(Level.INFO, "The UIParameter " + RendererUtils.getPathToComponent(param)
+                                        + " has a value of null and thus will not be added to the URL.");
+                    }
+                    // skip a null-value
+                    continue;
+                }
+                // add the param
+                if (params == null)
+                {
+                    params = new ArrayList<UIParameter>();
+                }
+                params.add(param);
+            }
+        }
+        if (params == null)
+        {
+            params = Collections.emptyList();
+        }
+        return params;
+    }
+
+}

Propchange: myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/renderkit/html/util/OutcomeTargetUtils.java
------------------------------------------------------------------------------
    svn:eol-style = native