Coverage Report - org.apache.myfaces.application.NavigationHandlerImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
NavigationHandlerImpl
0%
0/540
0%
0/444
6.786
NavigationHandlerImpl$1
N/A
N/A
6.786
NavigationHandlerImpl$KeyComparator
0%
0/2
N/A
6.786
NavigationHandlerImpl$NavigationContext
0%
0/41
0%
0/14
6.786
NavigationHandlerImpl$PreDisposeViewCallback
0%
0/3
N/A
6.786
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *   http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.myfaces.application;
 20  
 
 21  
 import java.io.IOException;
 22  
 import java.util.ArrayList;
 23  
 import java.util.Collection;
 24  
 import java.util.Collections;
 25  
 import java.util.Comparator;
 26  
 import java.util.EnumSet;
 27  
 import java.util.HashMap;
 28  
 import java.util.HashSet;
 29  
 import java.util.List;
 30  
 import java.util.Map;
 31  
 import java.util.Set;
 32  
 import java.util.concurrent.ConcurrentHashMap;
 33  
 import java.util.logging.Level;
 34  
 import java.util.logging.Logger;
 35  
 
 36  
 import java.util.regex.Pattern;
 37  
 import javax.el.MethodExpression;
 38  
 import javax.faces.FacesException;
 39  
 import javax.faces.application.ConfigurableNavigationHandler;
 40  
 import javax.faces.application.FacesMessage;
 41  
 import javax.faces.application.NavigationCase;
 42  
 import javax.faces.application.ProjectStage;
 43  
 import javax.faces.application.ViewHandler;
 44  
 import javax.faces.component.UIComponent;
 45  
 import javax.faces.component.UIViewAction;
 46  
 import javax.faces.component.UIViewRoot;
 47  
 import javax.faces.component.visit.VisitCallback;
 48  
 import javax.faces.component.visit.VisitContext;
 49  
 import javax.faces.component.visit.VisitHint;
 50  
 import javax.faces.component.visit.VisitResult;
 51  
 import javax.faces.context.ExternalContext;
 52  
 import javax.faces.context.FacesContext;
 53  
 import javax.faces.context.PartialViewContext;
 54  
 import javax.faces.flow.Flow;
 55  
 import javax.faces.flow.FlowCallNode;
 56  
 import javax.faces.flow.FlowHandler;
 57  
 import javax.faces.flow.FlowNode;
 58  
 import javax.faces.flow.MethodCallNode;
 59  
 import javax.faces.flow.Parameter;
 60  
 import javax.faces.flow.ReturnNode;
 61  
 import javax.faces.flow.SwitchCase;
 62  
 import javax.faces.flow.SwitchNode;
 63  
 import javax.faces.flow.ViewNode;
 64  
 import javax.faces.view.ViewDeclarationLanguage;
 65  
 import javax.faces.view.ViewMetadata;
 66  
 
 67  
 import org.apache.myfaces.config.RuntimeConfig;
 68  
 import org.apache.myfaces.config.element.NavigationRule;
 69  
 import org.apache.myfaces.flow.FlowHandlerImpl;
 70  
 import org.apache.myfaces.shared.application.NavigationUtils;
 71  
 import org.apache.myfaces.shared.renderkit.html.util.SharedStringBuilder;
 72  
 import org.apache.myfaces.shared.util.ClassUtils;
 73  
 import org.apache.myfaces.shared.util.HashMapUtils;
 74  
 import org.apache.myfaces.shared.util.StringUtils;
 75  
 import org.apache.myfaces.util.FilenameUtils;
 76  
 import org.apache.myfaces.view.facelets.ViewPoolProcessor;
 77  
 import org.apache.myfaces.view.facelets.tag.jsf.PreDisposeViewEvent;
 78  
 
 79  
 /**
 80  
  * @author Thomas Spiegl (latest modification by $Author$)
 81  
  * @author Anton Koinov
 82  
  * @version $Revision$ $Date$
 83  
  */
 84  
 public class NavigationHandlerImpl
 85  
     extends ConfigurableNavigationHandler
 86  
 {
 87  
     //private static final Log log = LogFactory.getLog(NavigationHandlerImpl.class);
 88  0
     private static final Logger log = Logger.getLogger(NavigationHandlerImpl.class.getName());
 89  
 
 90  
     private static final String SKIP_ITERATION_HINT = "javax.faces.visit.SKIP_ITERATION";
 91  
     
 92  0
     private static final Set<VisitHint> VISIT_HINTS = Collections.unmodifiableSet(
 93  
             EnumSet.of(VisitHint.SKIP_ITERATION));    
 94  
     
 95  
     private static final String OUTCOME_NAVIGATION_SB = "oam.navigation.OUTCOME_NAVIGATION_SB";
 96  
     
 97  0
     private static final Pattern AMP_PATTERN = Pattern.compile("&(amp;)?"); // "&" or "&amp;"
 98  
     
 99  
     private static final String ASTERISK = "*";
 100  
 
 101  0
     private Map<String, Set<NavigationCase>> _navigationCases = null;
 102  
     //private List<String> _wildcardKeys = new ArrayList<String>();
 103  0
     private List<_WildcardPattern> _wildcardPatterns = new ArrayList<_WildcardPattern>();
 104  
     private Boolean _developmentStage;
 105  
     
 106  0
     private Map<String, _FlowNavigationStructure> _flowNavigationStructureMap = 
 107  
         new ConcurrentHashMap<String, _FlowNavigationStructure>();
 108  
     
 109  
     private NavigationHandlerSupport navigationHandlerSupport;
 110  
 
 111  
     public NavigationHandlerImpl()
 112  0
     {
 113  0
         if (log.isLoggable(Level.FINEST))
 114  
         {
 115  0
             log.finest("New NavigationHandler instance created");
 116  
         }
 117  0
     }
 118  
 
 119  
     @Override
 120  
     public void handleNavigation(FacesContext facesContext, String fromAction, String outcome)
 121  
     {
 122  0
         handleNavigation(facesContext, fromAction, outcome, null);
 123  0
     }
 124  
 
 125  
     @Override
 126  
     public void handleNavigation(FacesContext facesContext, String fromAction, 
 127  
         String outcome, String toFlowDocumentId)
 128  
     {
 129  
         //NavigationCase navigationCase = getNavigationCase(facesContext, fromAction, outcome);
 130  0
         NavigationContext navigationContext = new NavigationContext();
 131  0
         NavigationCase navigationCase = null;
 132  
         try
 133  
         {
 134  0
             navigationCase = getNavigationCommand(facesContext, navigationContext, fromAction, outcome,
 135  
                 toFlowDocumentId);
 136  
         }
 137  
         finally
 138  
         {
 139  0
             navigationContext.finish(facesContext);
 140  0
         }
 141  
 
 142  0
         if (navigationCase != null)
 143  
         {
 144  0
             if (log.isLoggable(Level.FINEST))
 145  
             {
 146  0
                 log.finest("handleNavigation fromAction=" + fromAction + " outcome=" + outcome +
 147  
                           " toViewId =" + navigationCase.getToViewId(facesContext) +
 148  
                           " redirect=" + navigationCase.isRedirect());
 149  
             }
 150  0
             boolean isViewActionProcessingBroadcastAndRequiresRedirect = false;
 151  0
             if (UIViewAction.isProcessingBroadcast(facesContext))
 152  
             {
 153  
                 // f:viewAction tag always triggers a redirect to enforce execution of 
 154  
                 // the lifecycle again. Note this requires enables flash scope 
 155  
                 // keepMessages automatically, because a view action can add messages
 156  
                 // and these ones requires to be renderer afterwards.
 157  0
                 facesContext.getExternalContext().getFlash().setKeepMessages(true);
 158  0
                 String fromViewId = (facesContext.getViewRoot() == null) ? null :
 159  
                     facesContext.getViewRoot().getViewId();
 160  0
                 String toViewId = navigationCase.getToViewId(facesContext);
 161  
                 // A redirect is required only if the viewId changes. If the viewId
 162  
                 // does not change, section 7.4.2 says that a redirect/restart JSF
 163  
                 // lifecycle is not necessary.
 164  0
                 if (fromViewId == null && toViewId != null)
 165  
                 {
 166  0
                     isViewActionProcessingBroadcastAndRequiresRedirect = true;
 167  
                 }
 168  0
                 else if (fromViewId != null && !fromViewId.equals(toViewId))
 169  
                 {
 170  0
                     isViewActionProcessingBroadcastAndRequiresRedirect = true;
 171  
                 }
 172  
             }
 173  0
             if (navigationCase.isRedirect() || isViewActionProcessingBroadcastAndRequiresRedirect)
 174  
             { 
 175  
                 //&& (!PortletUtil.isPortletRequest(facesContext)))
 176  
                 // Spec section 7.4.2 says "redirects not possible" in this case for portlets
 177  
                 //But since the introduction of portlet bridge and the 
 178  
                 //removal of portlet code in myfaces core 2.0, this condition
 179  
                 //no longer applies
 180  
                 
 181  0
                 ExternalContext externalContext = facesContext.getExternalContext();
 182  0
                 ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
 183  0
                 String toViewId = navigationCase.getToViewId(facesContext);
 184  
                 
 185  0
                 String redirectPath = viewHandler.getRedirectURL(
 186  
                         facesContext, toViewId, 
 187  
                         NavigationUtils.getEvaluatedNavigationParameters(facesContext,
 188  
                         navigationCase.getParameters()) ,
 189  
                         navigationCase.isIncludeViewParams());
 190  
                 
 191  
                 // The spec doesn't say anything about how to handle redirect but it is
 192  
                 // better to apply the transition here where we have already calculated the
 193  
                 // route than add the parameters and delegate to 
 194  
                 // FlowHandler.clientWindowTransition(facesContext)
 195  0
                 applyFlowTransition(facesContext, navigationContext);
 196  
                 
 197  
                 //Clear ViewMap if we are redirecting to other resource
 198  0
                 UIViewRoot viewRoot = facesContext.getViewRoot(); 
 199  0
                 if (viewRoot != null && !toViewId.equals(viewRoot.getViewId()))
 200  
                 {
 201  
                     //call getViewMap(false) to prevent unnecessary map creation
 202  0
                     Map<String, Object> viewMap = viewRoot.getViewMap(false);
 203  0
                     if (viewMap != null)
 204  
                     {
 205  0
                         viewMap.clear();
 206  
                     }
 207  
                 }
 208  
                 
 209  
                 // JSF 2.0 the javadoc of handleNavigation() says something like this 
 210  
                 // "...If the view has changed after an application action, call
 211  
                 // PartialViewContext.setRenderAll(true)...". The effect is that ajax requests
 212  
                 // are included on navigation.
 213  0
                 PartialViewContext partialViewContext = facesContext.getPartialViewContext();
 214  0
                 String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
 215  0
                 if ( partialViewContext.isPartialRequest() && 
 216  
                      !partialViewContext.isRenderAll() && 
 217  
                      toViewId != null &&
 218  
                      !toViewId.equals(viewId))
 219  
                 {
 220  0
                     partialViewContext.setRenderAll(true);
 221  
                 }
 222  
 
 223  
                 // Dispose view if the view has been marked as disposable by default action listener
 224  0
                 ViewPoolProcessor processor = ViewPoolProcessor.getInstance(facesContext);
 225  0
                 if (processor != null && 
 226  
                     processor.isViewPoolEnabledForThisView(facesContext, facesContext.getViewRoot()))
 227  
                 {
 228  0
                     processor.disposeView(facesContext, facesContext.getViewRoot());
 229  
                 }
 230  
                 
 231  
                 // JSF 2.0 Spec call Flash.setRedirect(true) to notify Flash scope and take proper actions
 232  0
                 externalContext.getFlash().setRedirect(true);
 233  
                 try
 234  
                 {
 235  0
                     externalContext.redirect(redirectPath);
 236  0
                     facesContext.responseComplete();
 237  
                 }
 238  0
                 catch (IOException e)
 239  
                 {
 240  0
                     throw new FacesException(e.getMessage(), e);
 241  0
                 }
 242  0
             }
 243  
             else
 244  
             {
 245  0
                 ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
 246  
                 //create new view
 247  0
                 String newViewId = navigationCase.getToViewId(facesContext);
 248  
 
 249  
                 // JSF 2.0 the javadoc of handleNavigation() says something like this 
 250  
                 // "...If the view has changed after an application action, call
 251  
                 // PartialViewContext.setRenderAll(true)...". The effect is that ajax requests
 252  
                 // are included on navigation.
 253  0
                 PartialViewContext partialViewContext = facesContext.getPartialViewContext();
 254  0
                 String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
 255  0
                 if ( partialViewContext.isPartialRequest() && 
 256  
                      !partialViewContext.isRenderAll() && 
 257  
                      newViewId != null &&
 258  
                      !newViewId.equals(viewId))
 259  
                 {
 260  0
                     partialViewContext.setRenderAll(true);
 261  
                 }
 262  
 
 263  0
                 if (facesContext.getViewRoot() != null &&
 264  
                     facesContext.getViewRoot().getAttributes().containsKey("oam.CALL_PRE_DISPOSE_VIEW"))
 265  
                 {
 266  
                     try
 267  
                     {
 268  0
                         facesContext.getAttributes().put(SKIP_ITERATION_HINT, Boolean.TRUE);
 269  
 
 270  0
                         VisitContext visitContext = VisitContext.createVisitContext(facesContext, null, VISIT_HINTS);
 271  0
                         facesContext.getViewRoot().visitTree(visitContext,
 272  
                                                              new PreDisposeViewCallback());
 273  
                     }
 274  
                     finally
 275  
                     {
 276  0
                         facesContext.getAttributes().remove(SKIP_ITERATION_HINT);
 277  0
                     }
 278  
                 }
 279  
                 
 280  0
                 applyFlowTransition(facesContext, navigationContext);
 281  
 
 282  
                 // Dispose view if the view has been marked as disposable by default action listener
 283  0
                 ViewPoolProcessor processor = ViewPoolProcessor.getInstance(facesContext);
 284  0
                 if (processor != null && 
 285  
                     processor.isViewPoolEnabledForThisView(facesContext, facesContext.getViewRoot()))
 286  
                 {
 287  0
                     processor.disposeView(facesContext, facesContext.getViewRoot());
 288  
                 }
 289  
                 
 290  
                 // create UIViewRoot for new view
 291  0
                 UIViewRoot viewRoot = null;
 292  
                 
 293  0
                 String derivedViewId = viewHandler.deriveViewId(facesContext, newViewId);
 294  
 
 295  0
                 if (derivedViewId != null)
 296  
                 {
 297  0
                     ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(facesContext, derivedViewId);
 298  
                     
 299  0
                     if (vdl != null)
 300  
                     {
 301  0
                         ViewMetadata metadata = vdl.getViewMetadata(facesContext, newViewId);
 302  
                         
 303  0
                         if (metadata != null)
 304  
                         {
 305  0
                             viewRoot = metadata.createMetadataView(facesContext);
 306  
                         }
 307  
                     }
 308  
                 }
 309  
                 
 310  
                 // viewRoot can be null here, if ...
 311  
                 //   - we don't have a ViewDeclarationLanguage (e.g. when using facelets-1.x)
 312  
                 //   - there is no view metadata or metadata.createMetadataView() returned null
 313  
                 //   - viewHandler.deriveViewId() returned null
 314  0
                 if (viewRoot == null)
 315  
                 {
 316  0
                     viewRoot = viewHandler.createView(facesContext, newViewId);
 317  
                 }
 318  
                 
 319  0
                 facesContext.setViewRoot(viewRoot);
 320  0
                 facesContext.renderResponse();
 321  
             }
 322  0
         }
 323  
         else
 324  
         {
 325  
             // no navigationcase found, stay on current ViewRoot
 326  0
             if (log.isLoggable(Level.FINEST))
 327  
             {
 328  0
                 log.finest("handleNavigation fromAction=" + fromAction + " outcome=" + outcome +
 329  
                           " no matching navigation-case found, staying on current ViewRoot");
 330  
             }
 331  
         }
 332  0
     }
 333  
     
 334  
     private void applyFlowTransition(FacesContext facesContext, NavigationContext navigationContext)
 335  
     {
 336  
         //Apply Flow transition if any
 337  
         // Is any flow transition on the way?
 338  0
         if (navigationContext != null &&
 339  
             navigationContext.getSourceFlows() != null ||
 340  
             (navigationContext.getTargetFlows() != null &&
 341  
              !navigationContext.getTargetFlows().isEmpty()))
 342  
         {
 343  0
             FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
 344  0
             for (int i = 0; i < navigationContext.getTargetFlows().size(); i++)
 345  
             {
 346  0
                 Flow sourceFlow = navigationContext.getSourceFlows().get(i);
 347  0
                 Flow targetFlow = navigationContext.getTargetFlows().get(i);
 348  
 
 349  0
                 flowHandler.transition(facesContext, sourceFlow, targetFlow, 
 350  
                     navigationContext.getFlowCallNodes().get(i), 
 351  
                     navigationContext.getNavigationCase().getToViewId(facesContext));
 352  0
                 sourceFlow = targetFlow;
 353  
             }
 354  
         }
 355  0
     }
 356  
 
 357  
     /**
 358  
     * @return the navigationHandlerSupport
 359  
     */
 360  
     protected NavigationHandlerSupport getNavigationHandlerSupport()
 361  
     {
 362  0
         if (navigationHandlerSupport == null)
 363  
         {
 364  0
             navigationHandlerSupport = new DefaultNavigationHandlerSupport();
 365  
         }
 366  0
         return navigationHandlerSupport;
 367  
     }
 368  
 
 369  
     public void setNavigationHandlerSupport(NavigationHandlerSupport navigationHandlerSupport)
 370  
     {
 371  0
         this.navigationHandlerSupport = navigationHandlerSupport;
 372  0
     }
 373  
 
 374  0
     private static class PreDisposeViewCallback implements VisitCallback
 375  
     {
 376  
 
 377  
         public VisitResult visit(VisitContext context, UIComponent target)
 378  
         {
 379  0
             context.getFacesContext().getApplication().publishEvent(context.getFacesContext(),
 380  
                                                                     PreDisposeViewEvent.class, target);
 381  
             
 382  0
             return VisitResult.ACCEPT;
 383  
         }
 384  
     }
 385  
 
 386  
     /**
 387  
      * Returns the navigation case that applies for the given action and outcome
 388  
      */
 389  
     public NavigationCase getNavigationCase(FacesContext facesContext, String fromAction, String outcome)
 390  
     {
 391  0
         NavigationContext navigationContext = new NavigationContext();
 392  
         try
 393  
         {
 394  0
             return getNavigationCommand(facesContext, navigationContext, fromAction, outcome, null);
 395  
         }
 396  
         finally
 397  
         {
 398  0
             navigationContext.finish(facesContext);
 399  
         }
 400  
     }
 401  
     
 402  
     public NavigationCase getNavigationCommandFromGlobalNavigationCases(
 403  
         FacesContext facesContext, String viewId, NavigationContext navigationContext, 
 404  
         String fromAction, String outcome)
 405  
     {
 406  0
         Map<String, Set<NavigationCase>> casesMap = getNavigationCases();
 407  0
         NavigationCase navigationCase = null;
 408  
         
 409  
         Set<? extends NavigationCase> casesSet;
 410  0
         if (viewId != null)
 411  
         {
 412  0
             casesSet = casesMap.get(viewId);
 413  0
             if (casesSet != null)
 414  
             {
 415  
                 // Exact match?
 416  0
                 navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
 417  
             }
 418  
         }
 419  
 
 420  0
         if (navigationCase == null)
 421  
         {
 422  
             // Wildcard match?
 423  
             //List<String> sortedWildcardKeys = getSortedWildcardKeys();
 424  0
             List<_WildcardPattern> wildcardPatterns = getSortedWildcardPatterns();
 425  
             
 426  0
             for (int i = 0; i < wildcardPatterns.size(); i++)
 427  
             {
 428  0
                 _WildcardPattern wildcardPattern = wildcardPatterns.get(i);
 429  0
                 if (wildcardPattern.match(viewId))
 430  
                 {
 431  0
                     casesSet = casesMap.get(wildcardPattern.getPattern());
 432  0
                     if (casesSet != null)
 433  
                     {
 434  0
                         navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
 435  0
                         if (navigationCase != null)
 436  
                         {
 437  0
                             break;
 438  
                         }
 439  
                     }
 440  
                 }
 441  
             }
 442  
         }
 443  0
         return navigationCase;
 444  
     }
 445  
     
 446  
     private Flow calculateTargetFlow(FacesContext facesContext, String outcome, 
 447  
         FlowHandler flowHandler, List<Flow> activeFlows, String toFlowDocumentId)
 448  
     {
 449  0
         Flow targetFlow = null;
 450  0
         if (toFlowDocumentId != null)
 451  
         {
 452  0
             targetFlow = flowHandler.getFlow(facesContext, toFlowDocumentId, outcome);
 453  
         }
 454  0
         if (targetFlow == null && !activeFlows.isEmpty())
 455  
         {
 456  0
             for (Flow currentFlow : activeFlows)
 457  
             {
 458  0
                 targetFlow = flowHandler.getFlow(facesContext, currentFlow.getDefiningDocumentId(), outcome);
 459  0
                 if (targetFlow != null)
 460  
                 {
 461  0
                     break;
 462  
                 }
 463  0
             }
 464  
         }
 465  0
         if (targetFlow == null)
 466  
         {
 467  0
             targetFlow = flowHandler.getFlow(facesContext, "", outcome);
 468  
         }
 469  0
         return targetFlow;
 470  
     }
 471  
 
 472  
     public NavigationCase getNavigationCommand(
 473  
         FacesContext facesContext, NavigationContext navigationContext, String fromAction, String outcome, 
 474  
         String toFlowDocumentId)
 475  
     {
 476  0
         String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
 477  0
         NavigationCase navigationCase = getNavigationCommandFromGlobalNavigationCases(
 478  
                 facesContext, viewId, navigationContext, fromAction, outcome);
 479  0
         if (outcome != null && navigationCase == null)
 480  
         {
 481  0
             FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
 482  0
             List<Flow> activeFlows = FlowHandlerImpl.getActiveFlows(facesContext, flowHandler);
 483  
             // JSF 2.2 section 7.4.2: "... When outside of a flow, view identifier 
 484  
             // has the additional possibility of being a flow id.
 485  0
             Flow targetFlow = calculateTargetFlow(facesContext, outcome, flowHandler, activeFlows, toFlowDocumentId);
 486  0
             Flow currentFlow = navigationContext.getCurrentFlow(facesContext);
 487  0
             FlowCallNode targetFlowCallNode = null;
 488  0
             boolean startFlow = false;
 489  0
             String startFlowDocumentId = null;
 490  0
             String startFlowId = null;
 491  0
             boolean checkFlowNode = false;
 492  0
             String outcomeToGo = outcome;
 493  0
             String actionToGo = fromAction;
 494  
             
 495  0
             if (currentFlow != null)
 496  
             {
 497  
                 // JSF 2.2 section 7.4.2: When inside a flow, a view identifier has 
 498  
                 // the additional possibility of being the id of any node within the 
 499  
                 // current flow or the id of another flow
 500  0
                 if (targetFlow != null)
 501  
                 {
 502  0
                     if (flowHandler.isActive(facesContext, targetFlow.getDefiningDocumentId(), targetFlow.getId()))
 503  
                     {
 504  
                         // If the flow is already active, there is a chance that a node id has the same name as
 505  
                         // the flow and if that so, give preference to that node instead reenter into the flow.
 506  0
                         FlowNode flowNode = targetFlow.getNode(outcome);
 507  0
                         if (flowNode != null)
 508  
                         {
 509  0
                             checkFlowNode = true;
 510  
                         }
 511  
                         else
 512  
                         {
 513  0
                             startFlow = true;
 514  
                         }
 515  0
                     }
 516  
                     else
 517  
                     {
 518  0
                         startFlow = true;
 519  
                     }
 520  
                 }
 521  
                 else
 522  
                 {
 523  
                     // Check if thie 
 524  0
                     checkFlowNode = true;
 525  
                 }
 526  
             }
 527  
             else
 528  
             {
 529  0
                 if (targetFlow != null)
 530  
                 {
 531  
                     // start flow!
 532  0
                     startFlow = true;
 533  
                 }
 534  
             }
 535  0
             if (!startFlow)
 536  
             {
 537  0
                 for (Flow activeFlow : activeFlows)
 538  
                 {
 539  0
                     FlowNode node = activeFlow.getNode(outcome);
 540  0
                     if (node != null)
 541  
                     {
 542  0
                         currentFlow = activeFlow;
 543  0
                         break;
 544  
                     }
 545  0
                     _FlowNavigationStructure flowNavigationStructure = _flowNavigationStructureMap.get(
 546  
                             activeFlow.getId());
 547  0
                     navigationCase = getNavigationCaseFromFlowStructure(facesContext, 
 548  
                             flowNavigationStructure, fromAction, outcome, viewId);
 549  0
                     if (navigationCase != null)
 550  
                     {
 551  0
                         currentFlow = activeFlow;
 552  0
                         break;
 553  
                     }
 554  0
                 }
 555  
             }
 556  
             // If is necessary to enter a flow or there is a current
 557  
             // flow and it is necessary to check a flow node
 558  0
             if (startFlow || (checkFlowNode && currentFlow != null))
 559  
             {
 560  0
                 boolean complete = false;
 561  0
                 boolean checkNavCase = true;
 562  
 
 563  0
                 while (!complete && (startFlow || checkFlowNode))
 564  
                 {
 565  0
                     if (startFlow)
 566  
                     {
 567  0
                         if (flowHandler.isActive(facesContext, targetFlow.getDefiningDocumentId(), targetFlow.getId())
 568  
                             && targetFlowCallNode == null)
 569  
                         {
 570  
                             // Add the transition to exit from the flow
 571  0
                             Flow baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
 572  
                             // This is the part when the pseudo "recursive call" is done. 
 573  0
                             while (baseReturnFlow != null && !(baseReturnFlow.getDefiningDocumentId().equals(
 574  
                                     targetFlow.getDefiningDocumentId()) &&
 575  
                                    baseReturnFlow.getId().equals(targetFlow.getId())) )
 576  
                             {
 577  0
                                 navigationContext.popFlow(facesContext);
 578  0
                                 baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
 579  
                             }
 580  0
                             navigationContext.popFlow(facesContext);
 581  0
                             currentFlow = navigationContext.getCurrentFlow(facesContext);
 582  0
                             navigationContext.addTargetFlow(baseReturnFlow, currentFlow, null);
 583  
                         }
 584  0
                         if (startFlowId == null)
 585  
                         {
 586  0
                             startFlowDocumentId = targetFlow.getDefiningDocumentId();
 587  0
                             startFlowId = targetFlowCallNode == null ? targetFlow.getId() : targetFlowCallNode.getId();
 588  
                         }
 589  0
                         navigationContext.addTargetFlow(currentFlow, targetFlow, targetFlowCallNode);
 590  0
                         targetFlowCallNode = null;
 591  
                         // Since we start a new flow, the current flow is now the
 592  
                         // target flow.
 593  0
                         navigationContext.pushFlow(facesContext, targetFlow);
 594  0
                         currentFlow = targetFlow;
 595  
                         //No outboundCallNode.
 596  
                         //Resolve start node.
 597  0
                         outcomeToGo = resolveStartNodeOutcome(targetFlow);
 598  0
                         checkFlowNode = true;
 599  0
                         startFlow = false;
 600  
                     }
 601  0
                     if (checkFlowNode)
 602  
                     {
 603  0
                         FlowNode flowNode = currentFlow.getNode(outcomeToGo);
 604  0
                         if (flowNode != null)
 605  
                         {
 606  0
                             checkNavCase = true;
 607  0
                             if (!complete && flowNode instanceof SwitchNode)
 608  
                             {
 609  0
                                 outcomeToGo = calculateSwitchOutcome(facesContext, (SwitchNode) flowNode);
 610  
                                 // Start over again checking if the node exists.
 611  
                                 //fromAction = currentFlow.getId();
 612  0
                                 actionToGo = currentFlow.getId();
 613  0
                                 flowNode = currentFlow.getNode(outcomeToGo);
 614  0
                                 continue;
 615  
                             }
 616  0
                             if (!complete && flowNode instanceof FlowCallNode)
 617  
                             {
 618  
                                 // "... If the node is a FlowCallNode, save it aside as facesFlowCallNode. ..."
 619  0
                                 FlowCallNode flowCallNode = (FlowCallNode) flowNode;
 620  0
                                 targetFlow = calculateFlowCallTargetFlow(facesContext, 
 621  
                                     flowHandler, flowCallNode, currentFlow);
 622  0
                                 if (targetFlow != null)
 623  
                                 {
 624  0
                                     targetFlowCallNode = flowCallNode;
 625  0
                                     startFlow = true;
 626  0
                                     continue;
 627  
                                 }
 628  
                                 else
 629  
                                 {
 630  
                                     // Ask the FlowHandler for a Flow for this flowId, flowDocumentId pair. Obtain a 
 631  
                                     // reference to the start node and execute this algorithm again, on that start node.
 632  0
                                     complete = true;
 633  
                                 }
 634  
                             }
 635  0
                             if (!complete && flowNode instanceof MethodCallNode)
 636  
                             {
 637  0
                                 MethodCallNode methodCallNode = (MethodCallNode) flowNode;
 638  0
                                 String vdlViewIdentifier = calculateVdlViewIdentifier(facesContext, methodCallNode);
 639  
                                 // note a vdlViewIdentifier could be a flow node too
 640  0
                                 if (vdlViewIdentifier != null)
 641  
                                 {
 642  0
                                     outcomeToGo = vdlViewIdentifier;
 643  0
                                     actionToGo = currentFlow.getId();
 644  0
                                     continue;
 645  
                                 }
 646  
                                 else
 647  
                                 {
 648  0
                                     complete = true;
 649  
                                 }
 650  
                             }
 651  0
                             if (!complete && flowNode instanceof ReturnNode)
 652  
                             {
 653  0
                                 ReturnNode returnNode = (ReturnNode) flowNode;
 654  0
                                 String fromOutcome = returnNode.getFromOutcome(facesContext);
 655  0
                                 actionToGo = currentFlow.getId();
 656  0
                                 Flow sourceFlow = currentFlow;
 657  0
                                 Flow baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
 658  
                                 // This is the part when the pseudo "recursive call" is done. 
 659  0
                                 while (baseReturnFlow != null && !(baseReturnFlow.getDefiningDocumentId().equals(
 660  
                                         currentFlow.getDefiningDocumentId()) &&
 661  
                                        baseReturnFlow.getId().equals(currentFlow.getId())) )
 662  
                                 {
 663  0
                                     navigationContext.popFlow(facesContext);
 664  0
                                     baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
 665  
                                 }
 666  0
                                 navigationContext.popFlow(facesContext);
 667  0
                                 currentFlow = navigationContext.getCurrentFlow(facesContext);
 668  0
                                 navigationContext.addTargetFlow(sourceFlow, currentFlow, null);
 669  0
                                 outcomeToGo = fromOutcome;
 670  0
                                 String lastDisplayedViewId = navigationContext.getLastDisplayedViewId(facesContext, 
 671  
                                             currentFlow);
 672  
                                 
 673  
                                 // The part where FlowHandler.NULL_FLOW is passed as documentId causes the effect of
 674  
                                 // do not take into account the documentId of the returned flow in the command. In 
 675  
                                 // theory there is no Flow with defining documentId as FlowHandler.NULL_FLOW. It has 
 676  
                                 // sense because the one who specify the return rules should be the current flow 
 677  
                                 // after it is returned.
 678  0
                                 navigationCase = getNavigationCommand(facesContext, 
 679  
                                         navigationContext, actionToGo, outcomeToGo, FlowHandler.NULL_FLOW);
 680  0
                                 if (navigationCase != null)
 681  
                                 {
 682  0
                                     navigationCase = new FlowNavigationCase(navigationCase, 
 683  
                                         flowNode.getId(), FlowHandler.NULL_FLOW);
 684  0
                                     complete = true;
 685  
                                 }
 686  
                                 else
 687  
                                 {
 688  
                                     // No navigation case
 689  0
                                     if (lastDisplayedViewId != null)
 690  
                                     {
 691  0
                                         navigationCase = createNavigationCase(
 692  
                                             viewId, flowNode.getId(), lastDisplayedViewId, FlowHandler.NULL_FLOW);
 693  0
                                         complete = true;
 694  
                                     }
 695  
                                 }
 696  0
                                 if (currentFlow == null)
 697  
                                 {
 698  0
                                     complete = true;
 699  
                                 }
 700  
                                 continue;
 701  
                             }
 702  0
                             if (!complete && flowNode instanceof ViewNode)
 703  
                             {
 704  0
                                 ViewNode viewNode = (ViewNode) flowNode;
 705  0
                                 navigationCase = createNavigationCase(viewId, flowNode.getId(), 
 706  
                                     viewNode.getVdlDocumentId());
 707  0
                                 complete = true;
 708  0
                             }
 709  
                             else
 710  
                             {
 711  
                                 //Should not happen
 712  0
                                 complete = true;
 713  
                             }
 714  
                         }
 715  0
                         else if (checkNavCase)
 716  
                         {
 717  
                             // Not found in current flow.
 718  0
                             _FlowNavigationStructure flowNavigationStructure = _flowNavigationStructureMap.get(
 719  
                                     currentFlow.getId());
 720  0
                             navigationCase = getNavigationCaseFromFlowStructure(facesContext, 
 721  
                                     flowNavigationStructure, actionToGo, outcomeToGo, viewId);
 722  
                             
 723  
                             // JSF 2.2 section 7.4.2 "... any text that references a view identifier, such as 
 724  
                             // <from-view-id> or <to-view-id>,
 725  
                             // can also refer to a flow node ..."
 726  0
                             if (navigationCase != null)
 727  
                             {
 728  0
                                 outcomeToGo = navigationCase.getToViewId(facesContext);
 729  0
                                 checkNavCase = false;
 730  
                             }
 731  
                             else
 732  
                             {
 733  
                                 // No matter if navigationCase is null or not, complete the look.
 734  0
                                 complete = true;
 735  
                             }
 736  0
                         }
 737  
                         else
 738  
                         {
 739  0
                             complete = true;
 740  
                         }
 741  0
                     }
 742  
                 }
 743  
                 // Apply implicit navigation rules over outcomeToGo
 744  0
                 if (outcomeToGo != null && navigationCase == null)
 745  
                 {
 746  0
                     navigationCase = getOutcomeNavigationCase (facesContext, actionToGo, outcomeToGo);
 747  
                 }
 748  
             }
 749  0
             if (startFlowId != null)
 750  
             {
 751  0
                 navigationCase = new FlowNavigationCase(navigationCase, startFlowId, startFlowDocumentId);
 752  
             }
 753  
         }
 754  0
         if (outcome != null && navigationCase == null)
 755  
         {
 756  
             //if outcome is null, we don't check outcome based nav cases
 757  
             //otherwise, if navgiationCase is still null, check outcome-based nav cases
 758  0
             navigationCase = getOutcomeNavigationCase (facesContext, fromAction, outcome);
 759  
         }
 760  0
         if (outcome != null && navigationCase == null && !facesContext.isProjectStage(ProjectStage.Production))
 761  
         {
 762  0
             final FacesMessage facesMessage = new FacesMessage("No navigation case match for viewId " + viewId + 
 763  
                     ",  action " + fromAction + " and outcome " + outcome);
 764  0
             facesMessage.setSeverity(FacesMessage.SEVERITY_WARN);
 765  0
             facesContext.addMessage(null, facesMessage);
 766  
         }
 767  0
         if (navigationCase != null)
 768  
         {
 769  0
             navigationContext.setNavigationCase(navigationCase);
 770  
         }
 771  0
         return navigationContext.getNavigationCase();
 772  
         // if navigationCase == null, will stay on current view
 773  
     }
 774  
 
 775  
     private String resolveStartNodeOutcome(Flow targetFlow)
 776  
     {
 777  
         String outcomeToGo;
 778  0
         if (targetFlow.getStartNodeId() == null)
 779  
         {
 780  
             // In faces-config javadoc says:
 781  
             // "If there is no <start-node> element declared, it 
 782  
             //  is assumed to be <flowName>.xhtml."
 783  0
             outcomeToGo = "/" + targetFlow.getId()+ "/" + 
 784  
                 targetFlow.getId() + ".xhtml";
 785  
         }
 786  
         else
 787  
         {
 788  0
             outcomeToGo = targetFlow.getStartNodeId();
 789  
         }
 790  0
         return outcomeToGo;
 791  
     }
 792  
     
 793  
     private String calculateSwitchOutcome(FacesContext facesContext, SwitchNode switchNode)
 794  
     {
 795  0
         String outcomeToGo = null;
 796  0
         boolean resolved = false;
 797  
         // "... iterate over the NavigationCase instances returned from its getCases()
 798  
         // method. For each, one call getCondition(). If the result is true, let vdl 
 799  
         // view identifier be the value of its fromOutcome property.
 800  0
         for (SwitchCase switchCase : switchNode.getCases())
 801  
         {
 802  0
             Boolean isConditionTrue = switchCase.getCondition(facesContext);
 803  0
             if (Boolean.TRUE.equals(isConditionTrue))
 804  
             {
 805  0
                 outcomeToGo = switchCase.getFromOutcome();
 806  0
                 resolved = true;
 807  0
                 break;
 808  
             }
 809  0
         }
 810  0
         if (!resolved)
 811  
         {
 812  0
             outcomeToGo = switchNode.getDefaultOutcome(facesContext);
 813  
         }
 814  0
         return outcomeToGo;
 815  
     }
 816  
     
 817  
     private Flow calculateFlowCallTargetFlow(FacesContext facesContext, FlowHandler flowHandler,
 818  
         FlowCallNode flowCallNode, Flow currentFlow)
 819  
     {
 820  0
         Flow targetFlow = null;
 821  
         // " ... Let flowId be the value of its calledFlowId property and flowDocumentId 
 822  
         // be the value of its calledFlowDocumentId property. .."
 823  
 
 824  
         // " ... If no flowDocumentId exists for the node, let it be the string 
 825  
         // resulting from flowId + "/" + flowId + ".xhtml". ..." 
 826  
         // -=Leonardo Uribe=- flowDocumentId is inconsistent, waiting for answer of the EG,
 827  
         // for not let it be null.
 828  
 
 829  0
         String calledFlowDocumentId = flowCallNode.getCalledFlowDocumentId(facesContext);
 830  0
         if (calledFlowDocumentId == null)
 831  
         {
 832  0
             calledFlowDocumentId = currentFlow.getDefiningDocumentId();
 833  
         }
 834  0
         targetFlow = flowHandler.getFlow(facesContext, 
 835  
             calledFlowDocumentId, 
 836  
             flowCallNode.getCalledFlowId(facesContext));
 837  0
         if (targetFlow == null && !"".equals(calledFlowDocumentId))
 838  
         {
 839  0
             targetFlow = flowHandler.getFlow(facesContext, "", 
 840  
                 flowCallNode.getCalledFlowId(facesContext));
 841  
         }
 842  0
         return targetFlow;
 843  
     }
 844  
     
 845  
     private String calculateVdlViewIdentifier(FacesContext facesContext, MethodCallNode methodCallNode)
 846  
     {
 847  0
         String vdlViewIdentifier = null;
 848  0
         MethodExpression method = methodCallNode.getMethodExpression();
 849  0
         if (method != null)
 850  
         {
 851  0
             Object value = invokeMethodCallNode(facesContext, methodCallNode);
 852  0
             if (value != null)
 853  
             {
 854  0
                 vdlViewIdentifier = value.toString();
 855  
             }
 856  0
             else if (methodCallNode.getOutcome() != null)
 857  
             {
 858  0
                 vdlViewIdentifier = (String) methodCallNode.getOutcome().getValue(
 859  
                     facesContext.getELContext());
 860  
             }
 861  
         }
 862  0
         return vdlViewIdentifier;
 863  
     }
 864  
     
 865  
     private Object invokeMethodCallNode(FacesContext facesContext, MethodCallNode methodCallNode)
 866  
     {
 867  0
         MethodExpression method = methodCallNode.getMethodExpression();
 868  0
         Object value = null;
 869  
         
 870  0
         if (methodCallNode.getParameters() != null &&
 871  
             !methodCallNode.getParameters().isEmpty())
 872  
         {
 873  0
             Object[] parameters = new Object[methodCallNode.getParameters().size()];
 874  0
             Class[] clazzes = new Class[methodCallNode.getParameters().size()];
 875  0
             for (int i = 0; i < methodCallNode.getParameters().size(); i++)
 876  
             {
 877  0
                 Parameter param = methodCallNode.getParameters().get(i);
 878  0
                 parameters[i] = param.getValue().getValue(facesContext.getELContext());
 879  0
                 clazzes[i] = param.getName() != null ? 
 880  
                     ClassUtils.simpleJavaTypeToClass(param.getName()) :
 881  
                     (parameters[i] == null ? String.class : parameters[i].getClass());
 882  
             }
 883  
             
 884  
             // Now we need to recreate the EL method expression with the correct clazzes as parameters.
 885  
             // We should do it per invocation, because we don't know the parameter type.
 886  0
             method = facesContext.getApplication().getExpressionFactory().createMethodExpression(
 887  
                 facesContext.getELContext(), method.getExpressionString(), null, clazzes);
 888  0
             value = method.invoke(facesContext.getELContext(), parameters);
 889  0
         }
 890  
         else
 891  
         {
 892  0
             value = method.invoke(facesContext.getELContext(), null);
 893  
         }
 894  0
         return value;
 895  
     }
 896  
     
 897  
     private NavigationCase getNavigationCaseFromFlowStructure(FacesContext facesContext, 
 898  
             _FlowNavigationStructure flowNavigationStructure, String fromAction, String outcome, String viewId)
 899  
     {
 900  0
         Set<NavigationCase> casesSet = null;
 901  0
         NavigationCase navigationCase = null;
 902  
         
 903  0
         if (viewId != null)
 904  
         {
 905  0
             casesSet = flowNavigationStructure.getNavigationCases().get(viewId);
 906  0
             if (casesSet != null)
 907  
             {
 908  
                 // Exact match?
 909  0
                 navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
 910  
             }
 911  
         }
 912  0
         if (navigationCase == null)
 913  
         {
 914  0
             List<_WildcardPattern> wildcardPatterns = flowNavigationStructure.getWildcardKeys();
 915  0
             for (int i = 0; i < wildcardPatterns.size(); i++)
 916  
             {
 917  0
                 _WildcardPattern wildcardPattern = wildcardPatterns.get(i);
 918  0
                 if (wildcardPattern.match(viewId))
 919  
                 {
 920  0
                     casesSet = flowNavigationStructure.getNavigationCases().get(wildcardPattern.getPattern());
 921  0
                     if (casesSet != null)
 922  
                     {
 923  0
                         navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
 924  0
                         if (navigationCase != null)
 925  
                         {
 926  0
                             break;
 927  
                         }
 928  
                     }
 929  
                 }
 930  
             }
 931  
         }
 932  0
         return navigationCase;
 933  
     }
 934  
     
 935  
     /**
 936  
      * Derive a NavigationCase from a flow node. 
 937  
      * 
 938  
      * @param flowNode
 939  
      * @return 
 940  
      */
 941  
     private NavigationCase createNavigationCase(String fromViewId, String outcome, String toViewId)
 942  
     {
 943  0
         return new NavigationCase(fromViewId, null, outcome, null, toViewId, null, false, false);
 944  
     }
 945  
     
 946  
     private NavigationCase createNavigationCase(String fromViewId, String outcome, 
 947  
         String toViewId, String toFlowDocumentId)
 948  
     {
 949  0
         return new NavigationCase(fromViewId, null, outcome, null, toViewId, 
 950  
             toFlowDocumentId, null, false, false);
 951  
     }
 952  
     
 953  
     /**
 954  
      * Performs the algorithm specified in 7.4.2 for situations where no navigation cases are defined and instead
 955  
      * the navigation case is to be determined from the outcome.
 956  
      * 
 957  
      * TODO: cache results?
 958  
      */
 959  
     private NavigationCase getOutcomeNavigationCase (FacesContext facesContext, String fromAction, String outcome)
 960  
     {
 961  0
         String implicitViewId = null;
 962  0
         boolean includeViewParams = false;
 963  
         int index;
 964  0
         boolean isRedirect = false;
 965  0
         String queryString = null;
 966  0
         NavigationCase result = null;
 967  0
         String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
 968  
         //String viewIdToTest = outcome;
 969  0
         StringBuilder viewIdToTest = SharedStringBuilder.get(facesContext, OUTCOME_NAVIGATION_SB);
 970  0
         viewIdToTest.append(outcome);
 971  
         
 972  
         // If viewIdToTest contains a query string, remove it and set queryString with that value.
 973  0
         index = viewIdToTest.indexOf ("?");
 974  0
         if (index != -1)
 975  
         {
 976  0
             queryString = viewIdToTest.substring (index + 1);
 977  
             //viewIdToTest = viewIdToTest.substring (0, index);
 978  0
             viewIdToTest.setLength(index);
 979  
             
 980  
             // If queryString contains "faces-redirect=true", set isRedirect to true.
 981  0
             if (queryString.indexOf ("faces-redirect=true") != -1)
 982  
             {
 983  0
                 isRedirect = true;
 984  
             }
 985  
             
 986  
             // If queryString contains "includeViewParams=true" or 
 987  
             // "faces-include-view-params=true", set includeViewParams to true.
 988  0
             if (queryString.indexOf("includeViewParams=true") != -1 
 989  
                     || queryString.indexOf("faces-include-view-params=true") != -1)
 990  
             {
 991  0
                 includeViewParams = true;
 992  
             }
 993  
         }
 994  
         
 995  
         // If viewIdToTest does not have a "file extension", use the one from the current viewId.
 996  0
         index = viewIdToTest.indexOf (".");
 997  0
         if (index == -1)
 998  
         {
 999  0
             if (viewId != null)
 1000  
             {
 1001  0
                 index = viewId.lastIndexOf('.');
 1002  
 
 1003  0
                 if (index != -1)
 1004  
                 {
 1005  
                     //viewIdToTest += viewId.substring (index);
 1006  0
                     viewIdToTest.append(viewId.substring (index));
 1007  
                 }
 1008  
             }
 1009  
             else
 1010  
             {
 1011  
                 // This case happens when for for example there is a ViewExpiredException,
 1012  
                 // and a custom ExceptionHandler try to navigate using implicit navigation.
 1013  
                 // In this case, there is no UIViewRoot set on the FacesContext, so viewId 
 1014  
                 // is null.
 1015  
 
 1016  
                 // In this case, it should try to derive the viewId of the view that was
 1017  
                 // not able to restore, to get the extension and apply it to
 1018  
                 // the implicit navigation.
 1019  0
                 String tempViewId = getNavigationHandlerSupport().calculateViewId(facesContext);
 1020  0
                 if (tempViewId != null)
 1021  
                 {
 1022  0
                     index = tempViewId.lastIndexOf('.');
 1023  0
                     if(index != -1)
 1024  
                     {
 1025  0
                         viewIdToTest.append(tempViewId.substring (index));
 1026  
                     }
 1027  
                 }
 1028  
             }
 1029  0
             if (log.isLoggable(Level.FINEST))
 1030  
             {
 1031  0
                 log.finest("getOutcomeNavigationCase -> viewIdToTest: " + viewIdToTest);
 1032  
             } 
 1033  
         }
 1034  
 
 1035  
         // If viewIdToTest does not start with "/", look for the last "/" in viewId.  If not found, simply prepend "/".
 1036  
         // Otherwise, prepend everything before and including the last "/" in viewId.
 1037  
         
 1038  
         //if (!viewIdToTest.startsWith ("/") && viewId != null)
 1039  0
         boolean startWithSlash = false;
 1040  0
         if (viewIdToTest.length() > 0)
 1041  
         {
 1042  0
             startWithSlash = (viewIdToTest.charAt(0) == '/');
 1043  
         } 
 1044  0
         if (!startWithSlash) 
 1045  
         {
 1046  0
             index = -1;
 1047  0
             if( viewId != null )
 1048  
             {
 1049  0
                index = viewId.lastIndexOf('/');
 1050  
             }
 1051  
             
 1052  0
             if (index == -1)
 1053  
             {
 1054  
                 //viewIdToTest = "/" + viewIdToTest;
 1055  0
                 viewIdToTest.insert(0,"/");
 1056  
             }
 1057  
             
 1058  
             else
 1059  
             {
 1060  
                 //viewIdToTest = viewId.substring (0, index + 1) + viewIdToTest;
 1061  0
                 viewIdToTest.insert(0, viewId, 0, index + 1);
 1062  
             }
 1063  
         }
 1064  
         
 1065  
         // Apply normalization 
 1066  0
         String viewIdToTestString = null;
 1067  0
         boolean applyNormalization = false;
 1068  
         
 1069  0
         for (int i = 0; i < viewIdToTest.length()-1; i++)
 1070  
         {
 1071  0
             if (viewIdToTest.charAt(i) == '.' &&
 1072  
                 viewIdToTest.charAt(i+1) == '/')
 1073  
             {
 1074  0
                 applyNormalization = true; 
 1075  0
                 break;
 1076  
             }
 1077  
         }
 1078  0
         if (applyNormalization)
 1079  
         {
 1080  0
             viewIdToTestString = FilenameUtils.normalize(viewIdToTest.toString(), true);
 1081  
         }
 1082  
         else
 1083  
         {
 1084  0
             viewIdToTestString = viewIdToTest.toString();
 1085  
         }
 1086  
         
 1087  
         // Call ViewHandler.deriveViewId() and set the result as implicitViewId.
 1088  
         try
 1089  
         {
 1090  0
             implicitViewId = facesContext.getApplication().getViewHandler().deriveViewId (
 1091  
                     facesContext, viewIdToTestString);
 1092  
         }
 1093  
         
 1094  0
         catch (UnsupportedOperationException e)
 1095  
         {
 1096  
             // This is the case when a pre-JSF 2.0 ViewHandler is used.
 1097  
             // In this case, the default algorithm must be used.
 1098  
             // FIXME: I think we're always calling the "default" ViewHandler.deriveViewId() algorithm and we don't
 1099  
             // distinguish between pre-JSF 2.0 and JSF 2.0 ViewHandlers.  This probably needs to be addressed.
 1100  0
         }
 1101  
         
 1102  0
         if (implicitViewId != null)
 1103  
         {
 1104  
             // Append all params from the queryString
 1105  
             // (excluding faces-redirect, includeViewParams and faces-include-view-params)
 1106  0
             Map<String, List<String>> params = null;
 1107  0
             if (queryString != null && !"".equals(queryString))
 1108  
             {
 1109  
                 //String[] splitQueryParams = queryString.split("&(amp;)?"); // "&" or "&amp;"
 1110  0
                 String[] splitQueryParams = AMP_PATTERN.split(queryString); // "&" or "&amp;"
 1111  0
                 params = new HashMap<String, List<String>>(splitQueryParams.length, 
 1112  
                         (splitQueryParams.length* 4 + 3) / 3);
 1113  0
                 for (String queryParam : splitQueryParams)
 1114  
                 {
 1115  0
                     String[] splitParam = StringUtils.splitShortString(queryParam, '=');
 1116  0
                     if (splitParam.length == 2)
 1117  
                     {
 1118  
                         // valid parameter - add it to params
 1119  0
                         if ("includeViewParams".equals(splitParam[0])
 1120  
                                 || "faces-include-view-params".equals(splitParam[0])
 1121  
                                 || "faces-redirect".equals(splitParam[0]))
 1122  
                         {
 1123  
                             // ignore includeViewParams, faces-include-view-params and faces-redirect
 1124  0
                             continue;
 1125  
                         }
 1126  0
                         List<String> paramValues = params.get(splitParam[0]);
 1127  0
                         if (paramValues == null)
 1128  
                         {
 1129  
                             // no value for the given parameter yet
 1130  0
                             paramValues = new ArrayList<String>();
 1131  0
                             params.put(splitParam[0], paramValues);
 1132  
                         }
 1133  0
                         paramValues.add(splitParam[1]);
 1134  0
                     }
 1135  
                     else
 1136  
                     {
 1137  
                         // invalid parameter
 1138  0
                         throw new FacesException("Invalid parameter \"" + 
 1139  
                                 queryParam + "\" in outcome " + outcome);
 1140  
                     }
 1141  
                 }
 1142  
             }
 1143  
             
 1144  
             // Finally, create the NavigationCase.
 1145  0
             result = new NavigationCase (viewId, fromAction, outcome, null, 
 1146  
                     implicitViewId, params, isRedirect, includeViewParams);
 1147  
         }
 1148  
         
 1149  0
         return result;
 1150  
     }
 1151  
     
 1152  
     /**
 1153  
      * Returns the view ID that would be created for the given action and outcome
 1154  
      */
 1155  
     public String getViewId(FacesContext context, String fromAction, String outcome)
 1156  
     {
 1157  0
         return this.getNavigationCase(context, fromAction, outcome).getToViewId(context);
 1158  
     }
 1159  
 
 1160  
     /**
 1161  
      * TODO
 1162  
      * Invoked by the navigation handler before the new view component is created.
 1163  
      * @param viewId The view ID to be created
 1164  
      * @return The view ID that should be used instead. If null, the view ID passed
 1165  
      * in will be used without modification.
 1166  
      */
 1167  
     public String beforeNavigation(String viewId)
 1168  
     {
 1169  0
         return null;
 1170  
     }
 1171  
 
 1172  
     private NavigationCase calcMatchingNavigationCase(FacesContext context,
 1173  
                                                       Set<? extends NavigationCase> casesList,
 1174  
                                                       String actionRef,
 1175  
                                                       String outcome)
 1176  
     {
 1177  0
         NavigationCase noConditionCase = null;
 1178  0
         NavigationCase firstCase = null;
 1179  0
         NavigationCase firstCaseIf = null;
 1180  0
         NavigationCase secondCase = null;
 1181  0
         NavigationCase secondCaseIf = null;
 1182  0
         NavigationCase thirdCase = null;
 1183  0
         NavigationCase thirdCaseIf = null;
 1184  0
         NavigationCase fourthCase = null;
 1185  0
         NavigationCase fourthCaseIf = null;
 1186  
                         
 1187  0
         for (NavigationCase caze : casesList)
 1188  
         {
 1189  0
             String cazeOutcome = caze.getFromOutcome();
 1190  0
             String cazeActionRef = caze.getFromAction();
 1191  0
             Boolean cazeIf = caze.getCondition(context);
 1192  0
             boolean ifMatches = (cazeIf == null ? false : cazeIf.booleanValue());
 1193  
             // JSF 2.0: support conditional navigation via <if>.
 1194  
             // Use for later cases.
 1195  
             
 1196  0
             if(outcome == null && (cazeOutcome != null || cazeIf == null) && actionRef == null)
 1197  
             {
 1198  
                 //To match an outcome value of null, the <from-outcome> must be absent and the <if> element present.
 1199  0
                 continue;
 1200  
             }
 1201  
             
 1202  
             //If there are no conditions on navigation case save it and return as last resort
 1203  0
             if (cazeOutcome == null && cazeActionRef == null &&
 1204  
                 cazeIf == null && noConditionCase == null && outcome != null)
 1205  
             {
 1206  0
                 noConditionCase = caze;
 1207  
             }
 1208  
             
 1209  0
             if (cazeActionRef != null)
 1210  
             {
 1211  0
                 if (cazeOutcome != null)
 1212  
                 {
 1213  0
                     if ((actionRef != null) && (outcome != null) && cazeActionRef.equals (actionRef) &&
 1214  
                             cazeOutcome.equals (outcome))
 1215  
                     {
 1216  
                         // First case: match if <from-action> matches action and <from-outcome> matches outcome.
 1217  
                         // Caveat: evaluate <if> if available.
 1218  
 
 1219  0
                         if (cazeIf != null)
 1220  
                         {
 1221  0
                             if (ifMatches)
 1222  
                             {
 1223  0
                                 firstCaseIf = caze;
 1224  
                                 //return caze;
 1225  
                             }
 1226  
 
 1227  
                             continue;
 1228  
                         }
 1229  
                         else
 1230  
                         {
 1231  0
                             firstCase = caze;
 1232  
                             //return caze;
 1233  
                         }
 1234  
                     }
 1235  
                 }
 1236  
                 else
 1237  
                 {
 1238  0
                     if ((actionRef != null) && cazeActionRef.equals (actionRef))
 1239  
                     {
 1240  
                         // Third case: if only <from-action> specified, match against action.
 1241  
                         // Caveat: if <if> is available, evaluate.  If not, only match if outcome is not null.
 1242  
 
 1243  0
                         if (cazeIf != null)
 1244  
                         {
 1245  0
                             if (ifMatches)
 1246  
                             {
 1247  0
                                 thirdCaseIf = caze;
 1248  
                                 //return caze;
 1249  
                             }
 1250  
                             
 1251  
                             continue;
 1252  
                         }
 1253  
                         else
 1254  
                         {
 1255  0
                             if (outcome != null)
 1256  
                             {
 1257  0
                                 thirdCase = caze;
 1258  
                                 //return caze;
 1259  
                             }
 1260  
                             
 1261  
                             continue;
 1262  
                         }
 1263  
                     }
 1264  
                     else
 1265  
                     {
 1266  
                         // cazeActionRef != null and cazeOutcome == null
 1267  
                         // but cazeActionRef does not match. No additional operation
 1268  
                         // required because cazeIf is only taken into account 
 1269  
                         // it cazeActionRef match. 
 1270  
                         continue;
 1271  
                     }
 1272  
                 }
 1273  
             }
 1274  
             else
 1275  
             {
 1276  0
                 if (cazeOutcome != null && (outcome != null) && cazeOutcome.equals (outcome))
 1277  
                 {
 1278  
                     // Second case: if only <from-outcome> specified, match against outcome.
 1279  
                     // Caveat: if <if> is available, evaluate.
 1280  
 
 1281  0
                     if (cazeIf != null)
 1282  
                     {
 1283  0
                         if (ifMatches)
 1284  
                         {
 1285  0
                             secondCaseIf = caze;
 1286  
                             //return caze;
 1287  
                         }
 1288  
 
 1289  
                         continue;
 1290  
                     }
 1291  
                     else
 1292  
                     {
 1293  0
                         secondCase = caze;
 1294  
                         //return caze;
 1295  
                     }
 1296  
                 }
 1297  
             }
 1298  
 
 1299  
             // Fourth case: anything else matches if outcome is not null or <if> is specified.
 1300  
 
 1301  0
             if (outcome != null && cazeIf != null)
 1302  
             {
 1303  
                 // Again, if <if> present, evaluate.
 1304  0
                 if (ifMatches)
 1305  
                 {
 1306  0
                     fourthCaseIf = caze;
 1307  
                     //return caze;
 1308  
                 }
 1309  
 
 1310  
                 continue;
 1311  
             }
 1312  
 
 1313  0
             if ((cazeIf != null) && ifMatches)
 1314  
             {
 1315  0
                 fourthCase = caze;
 1316  
                 //return caze;
 1317  
             }
 1318  0
         }
 1319  
         
 1320  0
         if (firstCaseIf != null)
 1321  
         {
 1322  0
             return firstCaseIf;
 1323  
         }
 1324  0
         else if (firstCase != null)
 1325  
         {
 1326  0
             return firstCase;
 1327  
         }
 1328  0
         else if (secondCaseIf != null)
 1329  
         {
 1330  0
             return secondCaseIf;
 1331  
         }
 1332  0
         else if (secondCase != null)
 1333  
         {
 1334  0
             return secondCase;
 1335  
         }
 1336  0
         else if (thirdCaseIf != null)
 1337  
         {
 1338  0
             return thirdCaseIf;
 1339  
         }
 1340  0
         else if (thirdCase != null)
 1341  
         {
 1342  0
             return thirdCase;
 1343  
         }
 1344  0
         else if (fourthCaseIf != null)
 1345  
         {
 1346  0
             return fourthCaseIf;
 1347  
         }
 1348  0
         else if (fourthCase != null)
 1349  
         {
 1350  0
             return fourthCase;
 1351  
         }
 1352  
         
 1353  0
         return noConditionCase;
 1354  
     }
 1355  
 
 1356  
     private List<_WildcardPattern> getSortedWildcardPatterns()
 1357  
     {
 1358  0
         return _wildcardPatterns;
 1359  
     }
 1360  
 
 1361  
     @Override
 1362  
     public Map<String, Set<NavigationCase>> getNavigationCases()
 1363  
     {
 1364  0
         if (_developmentStage == null)
 1365  
         {
 1366  0
             _developmentStage = FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development);
 1367  
         }
 1368  0
         if (!Boolean.TRUE.equals(_developmentStage))
 1369  
         {
 1370  0
             if (_navigationCases == null)
 1371  
             {
 1372  0
                 FacesContext facesContext = FacesContext.getCurrentInstance();
 1373  0
                 ExternalContext externalContext = facesContext.getExternalContext();
 1374  0
                 RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
 1375  
                 
 1376  0
                 calculateNavigationCases(runtimeConfig);
 1377  
             }
 1378  0
             return _navigationCases;
 1379  
         }
 1380  
         else
 1381  
         {
 1382  0
             FacesContext facesContext = FacesContext.getCurrentInstance();
 1383  0
             ExternalContext externalContext = facesContext.getExternalContext();
 1384  0
             RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
 1385  
 
 1386  0
             if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
 1387  
             {
 1388  0
                 calculateNavigationCases(runtimeConfig);
 1389  
             }
 1390  0
             return _navigationCases;
 1391  
         }
 1392  
     }
 1393  
 
 1394  
     @Override
 1395  
     public void inspectFlow(FacesContext context, Flow flow)
 1396  
     {
 1397  0
         Map<String, Set<NavigationCase>> rules = flow.getNavigationCases();
 1398  0
         int rulesSize = rules.size();
 1399  
 
 1400  0
         Map<String, Set<NavigationCase>> cases = new HashMap<String, Set<NavigationCase>>(
 1401  
                 HashMapUtils.calcCapacity(rulesSize));
 1402  
 
 1403  0
         List<_WildcardPattern> wildcardPatterns = new ArrayList<_WildcardPattern>();
 1404  
 
 1405  0
         for (Map.Entry<String, Set<NavigationCase>> entry : rules.entrySet())
 1406  
         {
 1407  0
             String fromViewId = entry.getKey();
 1408  
 
 1409  
             //specification 7.4.2 footnote 4 - missing fromViewId is allowed:
 1410  0
             if (fromViewId == null)
 1411  
             {
 1412  0
                 fromViewId = ASTERISK;
 1413  
             }
 1414  
             else
 1415  
             {
 1416  0
                 fromViewId = fromViewId.trim();
 1417  
             }
 1418  
 
 1419  0
             Set<NavigationCase> set = cases.get(fromViewId);
 1420  0
             if (set == null)
 1421  
             {
 1422  0
                 set = new HashSet<NavigationCase>(entry.getValue());
 1423  0
                 cases.put(fromViewId, set);
 1424  0
                 if (fromViewId.endsWith(ASTERISK))
 1425  
                 {
 1426  0
                     wildcardPatterns.add(new _WildcardPattern(fromViewId));
 1427  
                 }
 1428  
             }
 1429  
             else
 1430  
             {
 1431  0
                 set.addAll(entry.getValue());
 1432  
             }
 1433  0
         }
 1434  
 
 1435  0
         Collections.sort(wildcardPatterns, new KeyComparator());
 1436  
 
 1437  0
         _flowNavigationStructureMap.put(
 1438  
             flow.getId(), 
 1439  
             new _FlowNavigationStructure(flow.getDefiningDocumentId(), flow.getId(), cases, wildcardPatterns) );
 1440  0
     }
 1441  
     
 1442  
     private synchronized void calculateNavigationCases(RuntimeConfig runtimeConfig)
 1443  
     {
 1444  0
         if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
 1445  
         {
 1446  0
             Collection<? extends NavigationRule> rules = runtimeConfig.getNavigationRules();
 1447  0
             int rulesSize = rules.size();
 1448  
 
 1449  0
             Map<String, Set<NavigationCase>> cases = new HashMap<String, Set<NavigationCase>>(
 1450  
                     HashMapUtils.calcCapacity(rulesSize));
 1451  
 
 1452  0
             List<_WildcardPattern> wildcardPatterns = new ArrayList<_WildcardPattern>();
 1453  
 
 1454  0
             for (NavigationRule rule : rules)
 1455  
             {
 1456  0
                 String fromViewId = rule.getFromViewId();
 1457  
 
 1458  
                 //specification 7.4.2 footnote 4 - missing fromViewId is allowed:
 1459  0
                 if (fromViewId == null)
 1460  
                 {
 1461  0
                     fromViewId = ASTERISK;
 1462  
                 }
 1463  
                 else
 1464  
                 {
 1465  0
                     fromViewId = fromViewId.trim();
 1466  
                 }
 1467  
 
 1468  0
                 Set<NavigationCase> set = cases.get(fromViewId);
 1469  0
                 if (set == null)
 1470  
                 {
 1471  0
                     set = new HashSet<NavigationCase>(convertNavigationCasesToAPI(rule));
 1472  0
                     cases.put(fromViewId, set);
 1473  0
                     if (fromViewId.endsWith(ASTERISK))
 1474  
                     {
 1475  0
                         wildcardPatterns.add(new _WildcardPattern(fromViewId));
 1476  
                     }
 1477  
                 }
 1478  
                 else
 1479  
                 {
 1480  0
                     set.addAll(convertNavigationCasesToAPI(rule));
 1481  
                 }
 1482  0
             }
 1483  
 
 1484  0
             Collections.sort(wildcardPatterns, new KeyComparator());
 1485  
 
 1486  0
             synchronized (cases)
 1487  
             {
 1488  
                 // We do not really need this sychronization at all, but this
 1489  
                 // gives us the peace of mind that some good optimizing compiler
 1490  
                 // will not rearrange the execution of the assignment to an
 1491  
                 // earlier time, before all init code completes
 1492  0
                 _navigationCases = cases;
 1493  0
                 _wildcardPatterns = wildcardPatterns;
 1494  
 
 1495  0
                 runtimeConfig.setNavigationRulesChanged(false);
 1496  0
             }
 1497  
         }
 1498  0
     }
 1499  
 
 1500  0
     private static final class KeyComparator implements Comparator<_WildcardPattern>
 1501  
     {
 1502  
         public int compare(_WildcardPattern s1, _WildcardPattern s2)
 1503  
         {
 1504  0
             return -s1.getPattern().compareTo(s2.getPattern());
 1505  
         }
 1506  
     }
 1507  
     
 1508  
     private Set<NavigationCase> convertNavigationCasesToAPI(NavigationRule rule)
 1509  
     {
 1510  0
         Collection<? extends org.apache.myfaces.config.element.NavigationCase> configCases = rule.getNavigationCases();
 1511  0
         Set<NavigationCase> apiCases = new HashSet<NavigationCase>(configCases.size());
 1512  
         
 1513  0
         for(org.apache.myfaces.config.element.NavigationCase configCase : configCases)
 1514  
         {   
 1515  0
             if(configCase.getRedirect() != null)
 1516  
             {
 1517  0
                 String includeViewParamsAttribute = configCase.getRedirect().getIncludeViewParams();
 1518  0
                 boolean includeViewParams = false; // default value is false
 1519  0
                 if (includeViewParamsAttribute != null)
 1520  
                 {
 1521  0
                     includeViewParams = Boolean.valueOf(includeViewParamsAttribute);
 1522  
                 }
 1523  0
                 apiCases.add(new NavigationCase(rule.getFromViewId(),configCase.getFromAction(),
 1524  
                                                 configCase.getFromOutcome(),configCase.getIf(),configCase.getToViewId(),
 1525  
                                                 configCase.getRedirect().getViewParams(),true,includeViewParams));
 1526  0
             }
 1527  
             else
 1528  
             {
 1529  0
                 apiCases.add(new NavigationCase(rule.getFromViewId(),configCase.getFromAction(),
 1530  
                                                 configCase.getFromOutcome(),configCase.getIf(),
 1531  
                                                 configCase.getToViewId(),null,false,false));
 1532  
             }
 1533  0
         }
 1534  
         
 1535  0
         return apiCases;
 1536  
     }
 1537  
     
 1538  
     /**
 1539  
      * A navigation command is an operation to do by the navigation handler like
 1540  
      * do a redirect, execute a normal navigation or enter or exit a flow. 
 1541  
      * 
 1542  
      * To resolve a navigation command, it is necessary to get an snapshot of the
 1543  
      * current "navigation context" and try to resolve the command.
 1544  
      */
 1545  
     protected static class NavigationContext
 1546  
     {
 1547  
         private NavigationCase navigationCase;
 1548  
         private List<Flow> sourceFlows;
 1549  
         private List<Flow> targetFlows;
 1550  
         private List<FlowCallNode> targetFlowCallNodes;
 1551  
         private List<Flow> currentFlows;
 1552  0
         private int returnCount = 0;
 1553  
 
 1554  
         public NavigationContext()
 1555  0
         {
 1556  0
         }
 1557  
 
 1558  
         public NavigationContext(NavigationCase navigationCase)
 1559  0
         {
 1560  0
             this.navigationCase = navigationCase;
 1561  0
         }
 1562  
         
 1563  
         public NavigationCase getNavigationCase()
 1564  
         {
 1565  0
             return navigationCase;
 1566  
         }
 1567  
 
 1568  
         public void setNavigationCase(NavigationCase navigationCase)
 1569  
         {
 1570  0
             this.navigationCase = navigationCase;
 1571  0
         }
 1572  
 
 1573  
         public List<Flow> getSourceFlows()
 1574  
         {
 1575  0
             return sourceFlows;
 1576  
         }
 1577  
 
 1578  
         public List<Flow> getTargetFlows()
 1579  
         {
 1580  0
             return targetFlows;
 1581  
         }
 1582  
         
 1583  
         public List<FlowCallNode> getFlowCallNodes()
 1584  
         {
 1585  0
             return targetFlowCallNodes;
 1586  
         }
 1587  
 
 1588  
         public void addTargetFlow(Flow sourceFlow, Flow targetFlow, FlowCallNode flowCallNode)
 1589  
         {
 1590  0
             if (targetFlows == null)
 1591  
             {
 1592  0
                 sourceFlows = new ArrayList<Flow>(4);
 1593  0
                 targetFlows = new ArrayList<Flow>(4);
 1594  0
                 targetFlowCallNodes = new ArrayList<FlowCallNode>(4);
 1595  
             }
 1596  0
             this.sourceFlows.add(sourceFlow);
 1597  0
             this.targetFlows.add(targetFlow);
 1598  0
             this.targetFlowCallNodes.add(flowCallNode);
 1599  0
         }
 1600  
         
 1601  
         public Flow getCurrentFlow(FacesContext facesContext)
 1602  
         {
 1603  0
             if (currentFlows != null && !currentFlows.isEmpty())
 1604  
             {
 1605  0
                 return currentFlows.get(currentFlows.size()-1);
 1606  
             }
 1607  
             else
 1608  
             {
 1609  0
                 FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
 1610  0
                 return flowHandler.getCurrentFlow(facesContext);
 1611  
             }
 1612  
         }
 1613  
         
 1614  
         public void finish(FacesContext facesContext)
 1615  
         {
 1616  
             // Get back flowHandler to its original state
 1617  0
             for (int i=0; i < returnCount; i++)
 1618  
             {
 1619  0
                 FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
 1620  0
                 flowHandler.popReturnMode(facesContext);
 1621  
             }
 1622  0
             returnCount = 0;
 1623  0
         }
 1624  
         
 1625  
         public void popFlow(FacesContext facesContext)
 1626  
         {
 1627  0
             if (currentFlows != null && !currentFlows.isEmpty())
 1628  
             {
 1629  0
                 currentFlows.remove(currentFlows.size()-1);
 1630  
             }
 1631  
             else
 1632  
             {
 1633  0
                 FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
 1634  0
                 flowHandler.pushReturnMode(facesContext);
 1635  0
                 returnCount++;
 1636  
             }
 1637  0
         }
 1638  
         
 1639  
         public void pushFlow(FacesContext facesContext, Flow flow)
 1640  
         {
 1641  0
             if (currentFlows == null)
 1642  
             {
 1643  0
                 currentFlows = new ArrayList<Flow>();
 1644  
             }
 1645  0
             currentFlows.add(flow);
 1646  0
         }
 1647  
         
 1648  
         public String getLastDisplayedViewId(FacesContext facesContext, Flow flow)
 1649  
         {
 1650  0
             FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
 1651  0
             return flowHandler.getLastDisplayedViewId(facesContext);
 1652  
         }
 1653  
     }
 1654  
 }