View Javadoc

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      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      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      private static final Pattern AMP_PATTERN = Pattern.compile("&(amp;)?"); // "&" or "&amp;"
98      
99      private static final String ASTERISK = "*";
100 
101     private Map<String, Set<NavigationCase>> _navigationCases = null;
102     //private List<String> _wildcardKeys = new ArrayList<String>();
103     private List<_WildcardPattern> _wildcardPatterns = new ArrayList<_WildcardPattern>();
104     private Boolean _developmentStage;
105     
106     private Map<String, _FlowNavigationStructure> _flowNavigationStructureMap = 
107         new ConcurrentHashMap<String, _FlowNavigationStructure>();
108     
109     private NavigationHandlerSupport navigationHandlerSupport;
110 
111     public NavigationHandlerImpl()
112     {
113         if (log.isLoggable(Level.FINEST))
114         {
115             log.finest("New NavigationHandler instance created");
116         }
117     }
118 
119     @Override
120     public void handleNavigation(FacesContext facesContext, String fromAction, String outcome)
121     {
122         handleNavigation(facesContext, fromAction, outcome, null);
123     }
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         NavigationContext navigationContext = new NavigationContext();
131         NavigationCase navigationCase = null;
132         try
133         {
134             navigationCase = getNavigationCommand(facesContext, navigationContext, fromAction, outcome,
135                 toFlowDocumentId);
136         }
137         finally
138         {
139             navigationContext.finish(facesContext);
140         }
141 
142         if (navigationCase != null)
143         {
144             if (log.isLoggable(Level.FINEST))
145             {
146                 log.finest("handleNavigation fromAction=" + fromAction + " outcome=" + outcome +
147                           " toViewId =" + navigationCase.getToViewId(facesContext) +
148                           " redirect=" + navigationCase.isRedirect());
149             }
150             boolean isViewActionProcessingBroadcastAndRequiresRedirect = false;
151             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                 facesContext.getExternalContext().getFlash().setKeepMessages(true);
158                 String fromViewId = (facesContext.getViewRoot() == null) ? null :
159                     facesContext.getViewRoot().getViewId();
160                 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                 if (fromViewId == null && toViewId != null)
165                 {
166                     isViewActionProcessingBroadcastAndRequiresRedirect = true;
167                 }
168                 else if (fromViewId != null && !fromViewId.equals(toViewId))
169                 {
170                     isViewActionProcessingBroadcastAndRequiresRedirect = true;
171                 }
172             }
173             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                 ExternalContext externalContext = facesContext.getExternalContext();
182                 ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
183                 String toViewId = navigationCase.getToViewId(facesContext);
184                 
185                 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                 applyFlowTransition(facesContext, navigationContext);
196                 
197                 //Clear ViewMap if we are redirecting to other resource
198                 UIViewRoot viewRoot = facesContext.getViewRoot(); 
199                 if (viewRoot != null && !toViewId.equals(viewRoot.getViewId()))
200                 {
201                     //call getViewMap(false) to prevent unnecessary map creation
202                     Map<String, Object> viewMap = viewRoot.getViewMap(false);
203                     if (viewMap != null)
204                     {
205                         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                 PartialViewContext partialViewContext = facesContext.getPartialViewContext();
214                 String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
215                 if ( partialViewContext.isPartialRequest() && 
216                      !partialViewContext.isRenderAll() && 
217                      toViewId != null &&
218                      !toViewId.equals(viewId))
219                 {
220                     partialViewContext.setRenderAll(true);
221                 }
222 
223                 // Dispose view if the view has been marked as disposable by default action listener
224                 ViewPoolProcessor processor = ViewPoolProcessor.getInstance(facesContext);
225                 if (processor != null && 
226                     processor.isViewPoolEnabledForThisView(facesContext, facesContext.getViewRoot()))
227                 {
228                     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                 externalContext.getFlash().setRedirect(true);
233                 try
234                 {
235                     externalContext.redirect(redirectPath);
236                     facesContext.responseComplete();
237                 }
238                 catch (IOException e)
239                 {
240                     throw new FacesException(e.getMessage(), e);
241                 }
242             }
243             else
244             {
245                 ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
246                 //create new view
247                 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                 PartialViewContext partialViewContext = facesContext.getPartialViewContext();
254                 String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
255                 if ( partialViewContext.isPartialRequest() && 
256                      !partialViewContext.isRenderAll() && 
257                      newViewId != null &&
258                      !newViewId.equals(viewId))
259                 {
260                     partialViewContext.setRenderAll(true);
261                 }
262 
263                 if (facesContext.getViewRoot() != null &&
264                     facesContext.getViewRoot().getAttributes().containsKey("oam.CALL_PRE_DISPOSE_VIEW"))
265                 {
266                     try
267                     {
268                         facesContext.getAttributes().put(SKIP_ITERATION_HINT, Boolean.TRUE);
269 
270                         VisitContext visitContext = VisitContext.createVisitContext(facesContext, null, VISIT_HINTS);
271                         facesContext.getViewRoot().visitTree(visitContext,
272                                                              new PreDisposeViewCallback());
273                     }
274                     finally
275                     {
276                         facesContext.getAttributes().remove(SKIP_ITERATION_HINT);
277                     }
278                 }
279                 
280                 applyFlowTransition(facesContext, navigationContext);
281 
282                 // Dispose view if the view has been marked as disposable by default action listener
283                 ViewPoolProcessor processor = ViewPoolProcessor.getInstance(facesContext);
284                 if (processor != null && 
285                     processor.isViewPoolEnabledForThisView(facesContext, facesContext.getViewRoot()))
286                 {
287                     processor.disposeView(facesContext, facesContext.getViewRoot());
288                 }
289                 
290                 // create UIViewRoot for new view
291                 UIViewRoot viewRoot = null;
292                 
293                 String derivedViewId = viewHandler.deriveViewId(facesContext, newViewId);
294 
295                 if (derivedViewId != null)
296                 {
297                     ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(facesContext, derivedViewId);
298                     
299                     if (vdl != null)
300                     {
301                         ViewMetadata metadata = vdl.getViewMetadata(facesContext, newViewId);
302                         
303                         if (metadata != null)
304                         {
305                             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                 if (viewRoot == null)
315                 {
316                     viewRoot = viewHandler.createView(facesContext, newViewId);
317                 }
318                 
319                 facesContext.setViewRoot(viewRoot);
320                 facesContext.renderResponse();
321             }
322         }
323         else
324         {
325             // no navigationcase found, stay on current ViewRoot
326             if (log.isLoggable(Level.FINEST))
327             {
328                 log.finest("handleNavigation fromAction=" + fromAction + " outcome=" + outcome +
329                           " no matching navigation-case found, staying on current ViewRoot");
330             }
331         }
332     }
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         if (navigationContext != null &&
339             navigationContext.getSourceFlows() != null ||
340             (navigationContext.getTargetFlows() != null &&
341              !navigationContext.getTargetFlows().isEmpty()))
342         {
343             FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
344             for (int i = 0; i < navigationContext.getTargetFlows().size(); i++)
345             {
346                 Flow sourceFlow = navigationContext.getSourceFlows().get(i);
347                 Flow targetFlow = navigationContext.getTargetFlows().get(i);
348 
349                 flowHandler.transition(facesContext, sourceFlow, targetFlow, 
350                     navigationContext.getFlowCallNodes().get(i), 
351                     navigationContext.getNavigationCase().getToViewId(facesContext));
352                 sourceFlow = targetFlow;
353             }
354         }
355     }
356 
357     /**
358     * @return the navigationHandlerSupport
359     */
360     protected NavigationHandlerSupport getNavigationHandlerSupport()
361     {
362         if (navigationHandlerSupport == null)
363         {
364             navigationHandlerSupport = new DefaultNavigationHandlerSupport();
365         }
366         return navigationHandlerSupport;
367     }
368 
369     public void setNavigationHandlerSupport(NavigationHandlerSupport navigationHandlerSupport)
370     {
371         this.navigationHandlerSupport = navigationHandlerSupport;
372     }
373 
374     private static class PreDisposeViewCallback implements VisitCallback
375     {
376 
377         public VisitResult visit(VisitContext context, UIComponent target)
378         {
379             context.getFacesContext().getApplication().publishEvent(context.getFacesContext(),
380                                                                     PreDisposeViewEvent.class, target);
381             
382             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         NavigationContext navigationContext = new NavigationContext();
392         try
393         {
394             return getNavigationCommand(facesContext, navigationContext, fromAction, outcome, null);
395         }
396         finally
397         {
398             navigationContext.finish(facesContext);
399         }
400     }
401     
402     public NavigationCase getNavigationCommandFromGlobalNavigationCases(
403         FacesContext facesContext, String viewId, NavigationContext navigationContext, 
404         String fromAction, String outcome)
405     {
406         Map<String, Set<NavigationCase>> casesMap = getNavigationCases();
407         NavigationCase navigationCase = null;
408         
409         Set<? extends NavigationCase> casesSet;
410         if (viewId != null)
411         {
412             casesSet = casesMap.get(viewId);
413             if (casesSet != null)
414             {
415                 // Exact match?
416                 navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
417             }
418         }
419 
420         if (navigationCase == null)
421         {
422             // Wildcard match?
423             //List<String> sortedWildcardKeys = getSortedWildcardKeys();
424             List<_WildcardPattern> wildcardPatterns = getSortedWildcardPatterns();
425             
426             for (int i = 0; i < wildcardPatterns.size(); i++)
427             {
428                 _WildcardPattern wildcardPattern = wildcardPatterns.get(i);
429                 if (wildcardPattern.match(viewId))
430                 {
431                     casesSet = casesMap.get(wildcardPattern.getPattern());
432                     if (casesSet != null)
433                     {
434                         navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
435                         if (navigationCase != null)
436                         {
437                             break;
438                         }
439                     }
440                 }
441             }
442         }
443         return navigationCase;
444     }
445     
446     private Flow calculateTargetFlow(FacesContext facesContext, String outcome, 
447         FlowHandler flowHandler, List<Flow> activeFlows, String toFlowDocumentId)
448     {
449         Flow targetFlow = null;
450         if (toFlowDocumentId != null)
451         {
452             targetFlow = flowHandler.getFlow(facesContext, toFlowDocumentId, outcome);
453         }
454         if (targetFlow == null && !activeFlows.isEmpty())
455         {
456             for (Flow currentFlow : activeFlows)
457             {
458                 targetFlow = flowHandler.getFlow(facesContext, currentFlow.getDefiningDocumentId(), outcome);
459                 if (targetFlow != null)
460                 {
461                     break;
462                 }
463             }
464         }
465         if (targetFlow == null)
466         {
467             targetFlow = flowHandler.getFlow(facesContext, "", outcome);
468         }
469         return targetFlow;
470     }
471 
472     public NavigationCase getNavigationCommand(
473         FacesContext facesContext, NavigationContext navigationContext, String fromAction, String outcome, 
474         String toFlowDocumentId)
475     {
476         String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
477         NavigationCase navigationCase = getNavigationCommandFromGlobalNavigationCases(
478                 facesContext, viewId, navigationContext, fromAction, outcome);
479         if (outcome != null && navigationCase == null)
480         {
481             FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
482             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             Flow targetFlow = calculateTargetFlow(facesContext, outcome, flowHandler, activeFlows, toFlowDocumentId);
486             Flow currentFlow = navigationContext.getCurrentFlow(facesContext);
487             FlowCallNode targetFlowCallNode = null;
488             boolean startFlow = false;
489             String startFlowDocumentId = null;
490             String startFlowId = null;
491             boolean checkFlowNode = false;
492             String outcomeToGo = outcome;
493             String actionToGo = fromAction;
494             
495             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                 if (targetFlow != null)
501                 {
502                     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                         FlowNode flowNode = targetFlow.getNode(outcome);
507                         if (flowNode != null)
508                         {
509                             checkFlowNode = true;
510                         }
511                         else
512                         {
513                             startFlow = true;
514                         }
515                     }
516                     else
517                     {
518                         startFlow = true;
519                     }
520                 }
521                 else
522                 {
523                     // Check if thie 
524                     checkFlowNode = true;
525                 }
526             }
527             else
528             {
529                 if (targetFlow != null)
530                 {
531                     // start flow!
532                     startFlow = true;
533                 }
534             }
535             if (!startFlow)
536             {
537                 for (Flow activeFlow : activeFlows)
538                 {
539                     FlowNode node = activeFlow.getNode(outcome);
540                     if (node != null)
541                     {
542                         currentFlow = activeFlow;
543                         break;
544                     }
545                     _FlowNavigationStructure flowNavigationStructure = _flowNavigationStructureMap.get(
546                             activeFlow.getId());
547                     navigationCase = getNavigationCaseFromFlowStructure(facesContext, 
548                             flowNavigationStructure, fromAction, outcome, viewId);
549                     if (navigationCase != null)
550                     {
551                         currentFlow = activeFlow;
552                         break;
553                     }
554                 }
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             if (startFlow || (checkFlowNode && currentFlow != null))
559             {
560                 boolean complete = false;
561                 boolean checkNavCase = true;
562 
563                 while (!complete && (startFlow || checkFlowNode))
564                 {
565                     if (startFlow)
566                     {
567                         if (flowHandler.isActive(facesContext, targetFlow.getDefiningDocumentId(), targetFlow.getId())
568                             && targetFlowCallNode == null)
569                         {
570                             // Add the transition to exit from the flow
571                             Flow baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
572                             // This is the part when the pseudo "recursive call" is done. 
573                             while (baseReturnFlow != null && !(baseReturnFlow.getDefiningDocumentId().equals(
574                                     targetFlow.getDefiningDocumentId()) &&
575                                    baseReturnFlow.getId().equals(targetFlow.getId())) )
576                             {
577                                 navigationContext.popFlow(facesContext);
578                                 baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
579                             }
580                             navigationContext.popFlow(facesContext);
581                             currentFlow = navigationContext.getCurrentFlow(facesContext);
582                             navigationContext.addTargetFlow(baseReturnFlow, currentFlow, null);
583                         }
584                         if (startFlowId == null)
585                         {
586                             startFlowDocumentId = targetFlow.getDefiningDocumentId();
587                             startFlowId = targetFlowCallNode == null ? targetFlow.getId() : targetFlowCallNode.getId();
588                         }
589                         navigationContext.addTargetFlow(currentFlow, targetFlow, targetFlowCallNode);
590                         targetFlowCallNode = null;
591                         // Since we start a new flow, the current flow is now the
592                         // target flow.
593                         navigationContext.pushFlow(facesContext, targetFlow);
594                         currentFlow = targetFlow;
595                         //No outboundCallNode.
596                         //Resolve start node.
597                         outcomeToGo = resolveStartNodeOutcome(targetFlow);
598                         checkFlowNode = true;
599                         startFlow = false;
600                     }
601                     if (checkFlowNode)
602                     {
603                         FlowNode flowNode = currentFlow.getNode(outcomeToGo);
604                         if (flowNode != null)
605                         {
606                             checkNavCase = true;
607                             if (!complete && flowNode instanceof SwitchNode)
608                             {
609                                 outcomeToGo = calculateSwitchOutcome(facesContext, (SwitchNode) flowNode);
610                                 // Start over again checking if the node exists.
611                                 //fromAction = currentFlow.getId();
612                                 actionToGo = currentFlow.getId();
613                                 flowNode = currentFlow.getNode(outcomeToGo);
614                                 continue;
615                             }
616                             if (!complete && flowNode instanceof FlowCallNode)
617                             {
618                                 // "... If the node is a FlowCallNode, save it aside as facesFlowCallNode. ..."
619                                 FlowCallNode flowCallNode = (FlowCallNode) flowNode;
620                                 targetFlow = calculateFlowCallTargetFlow(facesContext, 
621                                     flowHandler, flowCallNode, currentFlow);
622                                 if (targetFlow != null)
623                                 {
624                                     targetFlowCallNode = flowCallNode;
625                                     startFlow = true;
626                                     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                                     complete = true;
633                                 }
634                             }
635                             if (!complete && flowNode instanceof MethodCallNode)
636                             {
637                                 MethodCallNode methodCallNode = (MethodCallNode) flowNode;
638                                 String vdlViewIdentifier = calculateVdlViewIdentifier(facesContext, methodCallNode);
639                                 // note a vdlViewIdentifier could be a flow node too
640                                 if (vdlViewIdentifier != null)
641                                 {
642                                     outcomeToGo = vdlViewIdentifier;
643                                     actionToGo = currentFlow.getId();
644                                     continue;
645                                 }
646                                 else
647                                 {
648                                     complete = true;
649                                 }
650                             }
651                             if (!complete && flowNode instanceof ReturnNode)
652                             {
653                                 ReturnNode returnNode = (ReturnNode) flowNode;
654                                 String fromOutcome = returnNode.getFromOutcome(facesContext);
655                                 actionToGo = currentFlow.getId();
656                                 Flow sourceFlow = currentFlow;
657                                 Flow baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
658                                 // This is the part when the pseudo "recursive call" is done. 
659                                 while (baseReturnFlow != null && !(baseReturnFlow.getDefiningDocumentId().equals(
660                                         currentFlow.getDefiningDocumentId()) &&
661                                        baseReturnFlow.getId().equals(currentFlow.getId())) )
662                                 {
663                                     navigationContext.popFlow(facesContext);
664                                     baseReturnFlow = navigationContext.getCurrentFlow(facesContext);
665                                 }
666                                 navigationContext.popFlow(facesContext);
667                                 currentFlow = navigationContext.getCurrentFlow(facesContext);
668                                 navigationContext.addTargetFlow(sourceFlow, currentFlow, null);
669                                 outcomeToGo = fromOutcome;
670                                 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                                 navigationCase = getNavigationCommand(facesContext, 
679                                         navigationContext, actionToGo, outcomeToGo, FlowHandler.NULL_FLOW);
680                                 if (navigationCase != null)
681                                 {
682                                     navigationCase = new FlowNavigationCase(navigationCase, 
683                                         flowNode.getId(), FlowHandler.NULL_FLOW);
684                                     complete = true;
685                                 }
686                                 else
687                                 {
688                                     // No navigation case
689                                     if (lastDisplayedViewId != null)
690                                     {
691                                         navigationCase = createNavigationCase(
692                                             viewId, flowNode.getId(), lastDisplayedViewId, FlowHandler.NULL_FLOW);
693                                         complete = true;
694                                     }
695                                 }
696                                 if (currentFlow == null)
697                                 {
698                                     complete = true;
699                                 }
700                                 continue;
701                             }
702                             if (!complete && flowNode instanceof ViewNode)
703                             {
704                                 ViewNode viewNode = (ViewNode) flowNode;
705                                 navigationCase = createNavigationCase(viewId, flowNode.getId(), 
706                                     viewNode.getVdlDocumentId());
707                                 complete = true;
708                             }
709                             else
710                             {
711                                 //Should not happen
712                                 complete = true;
713                             }
714                         }
715                         else if (checkNavCase)
716                         {
717                             // Not found in current flow.
718                             _FlowNavigationStructure flowNavigationStructure = _flowNavigationStructureMap.get(
719                                     currentFlow.getId());
720                             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                             if (navigationCase != null)
727                             {
728                                 outcomeToGo = navigationCase.getToViewId(facesContext);
729                                 checkNavCase = false;
730                             }
731                             else
732                             {
733                                 // No matter if navigationCase is null or not, complete the look.
734                                 complete = true;
735                             }
736                         }
737                         else
738                         {
739                             complete = true;
740                         }
741                     }
742                 }
743                 // Apply implicit navigation rules over outcomeToGo
744                 if (outcomeToGo != null && navigationCase == null)
745                 {
746                     navigationCase = getOutcomeNavigationCase (facesContext, actionToGo, outcomeToGo);
747                 }
748             }
749             if (startFlowId != null)
750             {
751                 navigationCase = new FlowNavigationCase(navigationCase, startFlowId, startFlowDocumentId);
752             }
753         }
754         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             navigationCase = getOutcomeNavigationCase (facesContext, fromAction, outcome);
759         }
760         if (outcome != null && navigationCase == null && !facesContext.isProjectStage(ProjectStage.Production))
761         {
762             final FacesMessage facesMessage = new FacesMessage("No navigation case match for viewId " + viewId + 
763                     ",  action " + fromAction + " and outcome " + outcome);
764             facesMessage.setSeverity(FacesMessage.SEVERITY_WARN);
765             facesContext.addMessage(null, facesMessage);
766         }
767         if (navigationCase != null)
768         {
769             navigationContext.setNavigationCase(navigationCase);
770         }
771         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         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             outcomeToGo = "/" + targetFlow.getId()+ "/" + 
784                 targetFlow.getId() + ".xhtml";
785         }
786         else
787         {
788             outcomeToGo = targetFlow.getStartNodeId();
789         }
790         return outcomeToGo;
791     }
792     
793     private String calculateSwitchOutcome(FacesContext facesContext, SwitchNode switchNode)
794     {
795         String outcomeToGo = null;
796         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         for (SwitchCase switchCase : switchNode.getCases())
801         {
802             Boolean isConditionTrue = switchCase.getCondition(facesContext);
803             if (Boolean.TRUE.equals(isConditionTrue))
804             {
805                 outcomeToGo = switchCase.getFromOutcome();
806                 resolved = true;
807                 break;
808             }
809         }
810         if (!resolved)
811         {
812             outcomeToGo = switchNode.getDefaultOutcome(facesContext);
813         }
814         return outcomeToGo;
815     }
816     
817     private Flow calculateFlowCallTargetFlow(FacesContext facesContext, FlowHandler flowHandler,
818         FlowCallNode flowCallNode, Flow currentFlow)
819     {
820         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         String calledFlowDocumentId = flowCallNode.getCalledFlowDocumentId(facesContext);
830         if (calledFlowDocumentId == null)
831         {
832             calledFlowDocumentId = currentFlow.getDefiningDocumentId();
833         }
834         targetFlow = flowHandler.getFlow(facesContext, 
835             calledFlowDocumentId, 
836             flowCallNode.getCalledFlowId(facesContext));
837         if (targetFlow == null && !"".equals(calledFlowDocumentId))
838         {
839             targetFlow = flowHandler.getFlow(facesContext, "", 
840                 flowCallNode.getCalledFlowId(facesContext));
841         }
842         return targetFlow;
843     }
844     
845     private String calculateVdlViewIdentifier(FacesContext facesContext, MethodCallNode methodCallNode)
846     {
847         String vdlViewIdentifier = null;
848         MethodExpression method = methodCallNode.getMethodExpression();
849         if (method != null)
850         {
851             Object value = invokeMethodCallNode(facesContext, methodCallNode);
852             if (value != null)
853             {
854                 vdlViewIdentifier = value.toString();
855             }
856             else if (methodCallNode.getOutcome() != null)
857             {
858                 vdlViewIdentifier = (String) methodCallNode.getOutcome().getValue(
859                     facesContext.getELContext());
860             }
861         }
862         return vdlViewIdentifier;
863     }
864     
865     private Object invokeMethodCallNode(FacesContext facesContext, MethodCallNode methodCallNode)
866     {
867         MethodExpression method = methodCallNode.getMethodExpression();
868         Object value = null;
869         
870         if (methodCallNode.getParameters() != null &&
871             !methodCallNode.getParameters().isEmpty())
872         {
873             Object[] parameters = new Object[methodCallNode.getParameters().size()];
874             Class[] clazzes = new Class[methodCallNode.getParameters().size()];
875             for (int i = 0; i < methodCallNode.getParameters().size(); i++)
876             {
877                 Parameter param = methodCallNode.getParameters().get(i);
878                 parameters[i] = param.getValue().getValue(facesContext.getELContext());
879                 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             method = facesContext.getApplication().getExpressionFactory().createMethodExpression(
887                 facesContext.getELContext(), method.getExpressionString(), null, clazzes);
888             value = method.invoke(facesContext.getELContext(), parameters);
889         }
890         else
891         {
892             value = method.invoke(facesContext.getELContext(), null);
893         }
894         return value;
895     }
896     
897     private NavigationCase getNavigationCaseFromFlowStructure(FacesContext facesContext, 
898             _FlowNavigationStructure flowNavigationStructure, String fromAction, String outcome, String viewId)
899     {
900         Set<NavigationCase> casesSet = null;
901         NavigationCase navigationCase = null;
902         
903         if (viewId != null)
904         {
905             casesSet = flowNavigationStructure.getNavigationCases().get(viewId);
906             if (casesSet != null)
907             {
908                 // Exact match?
909                 navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
910             }
911         }
912         if (navigationCase == null)
913         {
914             List<_WildcardPattern> wildcardPatterns = flowNavigationStructure.getWildcardKeys();
915             for (int i = 0; i < wildcardPatterns.size(); i++)
916             {
917                 _WildcardPattern wildcardPattern = wildcardPatterns.get(i);
918                 if (wildcardPattern.match(viewId))
919                 {
920                     casesSet = flowNavigationStructure.getNavigationCases().get(wildcardPattern.getPattern());
921                     if (casesSet != null)
922                     {
923                         navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
924                         if (navigationCase != null)
925                         {
926                             break;
927                         }
928                     }
929                 }
930             }
931         }
932         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         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         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         String implicitViewId = null;
962         boolean includeViewParams = false;
963         int index;
964         boolean isRedirect = false;
965         String queryString = null;
966         NavigationCase result = null;
967         String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
968         //String viewIdToTest = outcome;
969         StringBuilder viewIdToTest = SharedStringBuilder.get(facesContext, OUTCOME_NAVIGATION_SB);
970         viewIdToTest.append(outcome);
971         
972         // If viewIdToTest contains a query string, remove it and set queryString with that value.
973         index = viewIdToTest.indexOf ("?");
974         if (index != -1)
975         {
976             queryString = viewIdToTest.substring (index + 1);
977             //viewIdToTest = viewIdToTest.substring (0, index);
978             viewIdToTest.setLength(index);
979             
980             // If queryString contains "faces-redirect=true", set isRedirect to true.
981             if (queryString.indexOf ("faces-redirect=true") != -1)
982             {
983                 isRedirect = true;
984             }
985             
986             // If queryString contains "includeViewParams=true" or 
987             // "faces-include-view-params=true", set includeViewParams to true.
988             if (queryString.indexOf("includeViewParams=true") != -1 
989                     || queryString.indexOf("faces-include-view-params=true") != -1)
990             {
991                 includeViewParams = true;
992             }
993         }
994         
995         // If viewIdToTest does not have a "file extension", use the one from the current viewId.
996         index = viewIdToTest.indexOf (".");
997         if (index == -1)
998         {
999             if (viewId != null)
1000             {
1001                 index = viewId.lastIndexOf('.');
1002 
1003                 if (index != -1)
1004                 {
1005                     //viewIdToTest += viewId.substring (index);
1006                     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                 String tempViewId = getNavigationHandlerSupport().calculateViewId(facesContext);
1020                 if (tempViewId != null)
1021                 {
1022                     index = tempViewId.lastIndexOf('.');
1023                     if(index != -1)
1024                     {
1025                         viewIdToTest.append(tempViewId.substring (index));
1026                     }
1027                 }
1028             }
1029             if (log.isLoggable(Level.FINEST))
1030             {
1031                 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         boolean startWithSlash = false;
1040         if (viewIdToTest.length() > 0)
1041         {
1042             startWithSlash = (viewIdToTest.charAt(0) == '/');
1043         } 
1044         if (!startWithSlash) 
1045         {
1046             index = -1;
1047             if( viewId != null )
1048             {
1049                index = viewId.lastIndexOf('/');
1050             }
1051             
1052             if (index == -1)
1053             {
1054                 //viewIdToTest = "/" + viewIdToTest;
1055                 viewIdToTest.insert(0,"/");
1056             }
1057             
1058             else
1059             {
1060                 //viewIdToTest = viewId.substring (0, index + 1) + viewIdToTest;
1061                 viewIdToTest.insert(0, viewId, 0, index + 1);
1062             }
1063         }
1064         
1065         // Apply normalization 
1066         String viewIdToTestString = null;
1067         boolean applyNormalization = false;
1068         
1069         for (int i = 0; i < viewIdToTest.length()-1; i++)
1070         {
1071             if (viewIdToTest.charAt(i) == '.' &&
1072                 viewIdToTest.charAt(i+1) == '/')
1073             {
1074                 applyNormalization = true; 
1075                 break;
1076             }
1077         }
1078         if (applyNormalization)
1079         {
1080             viewIdToTestString = FilenameUtils.normalize(viewIdToTest.toString(), true);
1081         }
1082         else
1083         {
1084             viewIdToTestString = viewIdToTest.toString();
1085         }
1086         
1087         // Call ViewHandler.deriveViewId() and set the result as implicitViewId.
1088         try
1089         {
1090             implicitViewId = facesContext.getApplication().getViewHandler().deriveViewId (
1091                     facesContext, viewIdToTestString);
1092         }
1093         
1094         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         }
1101         
1102         if (implicitViewId != null)
1103         {
1104             // Append all params from the queryString
1105             // (excluding faces-redirect, includeViewParams and faces-include-view-params)
1106             Map<String, List<String>> params = null;
1107             if (queryString != null && !"".equals(queryString))
1108             {
1109                 //String[] splitQueryParams = queryString.split("&(amp;)?"); // "&" or "&amp;"
1110                 String[] splitQueryParams = AMP_PATTERN.split(queryString); // "&" or "&amp;"
1111                 params = new HashMap<String, List<String>>(splitQueryParams.length, 
1112                         (splitQueryParams.length* 4 + 3) / 3);
1113                 for (String queryParam : splitQueryParams)
1114                 {
1115                     String[] splitParam = StringUtils.splitShortString(queryParam, '=');
1116                     if (splitParam.length == 2)
1117                     {
1118                         // valid parameter - add it to params
1119                         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                             continue;
1125                         }
1126                         List<String> paramValues = params.get(splitParam[0]);
1127                         if (paramValues == null)
1128                         {
1129                             // no value for the given parameter yet
1130                             paramValues = new ArrayList<String>();
1131                             params.put(splitParam[0], paramValues);
1132                         }
1133                         paramValues.add(splitParam[1]);
1134                     }
1135                     else
1136                     {
1137                         // invalid parameter
1138                         throw new FacesException("Invalid parameter \"" + 
1139                                 queryParam + "\" in outcome " + outcome);
1140                     }
1141                 }
1142             }
1143             
1144             // Finally, create the NavigationCase.
1145             result = new NavigationCase (viewId, fromAction, outcome, null, 
1146                     implicitViewId, params, isRedirect, includeViewParams);
1147         }
1148         
1149         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         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         return null;
1170     }
1171 
1172     private NavigationCase calcMatchingNavigationCase(FacesContext context,
1173                                                       Set<? extends NavigationCase> casesList,
1174                                                       String actionRef,
1175                                                       String outcome)
1176     {
1177         NavigationCase noConditionCase = null;
1178         NavigationCase firstCase = null;
1179         NavigationCase firstCaseIf = null;
1180         NavigationCase secondCase = null;
1181         NavigationCase secondCaseIf = null;
1182         NavigationCase thirdCase = null;
1183         NavigationCase thirdCaseIf = null;
1184         NavigationCase fourthCase = null;
1185         NavigationCase fourthCaseIf = null;
1186                         
1187         for (NavigationCase caze : casesList)
1188         {
1189             String cazeOutcome = caze.getFromOutcome();
1190             String cazeActionRef = caze.getFromAction();
1191             Boolean cazeIf = caze.getCondition(context);
1192             boolean ifMatches = (cazeIf == null ? false : cazeIf.booleanValue());
1193             // JSF 2.0: support conditional navigation via <if>.
1194             // Use for later cases.
1195             
1196             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                 continue;
1200             }
1201             
1202             //If there are no conditions on navigation case save it and return as last resort
1203             if (cazeOutcome == null && cazeActionRef == null &&
1204                 cazeIf == null && noConditionCase == null && outcome != null)
1205             {
1206                 noConditionCase = caze;
1207             }
1208             
1209             if (cazeActionRef != null)
1210             {
1211                 if (cazeOutcome != null)
1212                 {
1213                     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                         if (cazeIf != null)
1220                         {
1221                             if (ifMatches)
1222                             {
1223                                 firstCaseIf = caze;
1224                                 //return caze;
1225                             }
1226 
1227                             continue;
1228                         }
1229                         else
1230                         {
1231                             firstCase = caze;
1232                             //return caze;
1233                         }
1234                     }
1235                 }
1236                 else
1237                 {
1238                     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                         if (cazeIf != null)
1244                         {
1245                             if (ifMatches)
1246                             {
1247                                 thirdCaseIf = caze;
1248                                 //return caze;
1249                             }
1250                             
1251                             continue;
1252                         }
1253                         else
1254                         {
1255                             if (outcome != null)
1256                             {
1257                                 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                 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                     if (cazeIf != null)
1282                     {
1283                         if (ifMatches)
1284                         {
1285                             secondCaseIf = caze;
1286                             //return caze;
1287                         }
1288 
1289                         continue;
1290                     }
1291                     else
1292                     {
1293                         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             if (outcome != null && cazeIf != null)
1302             {
1303                 // Again, if <if> present, evaluate.
1304                 if (ifMatches)
1305                 {
1306                     fourthCaseIf = caze;
1307                     //return caze;
1308                 }
1309 
1310                 continue;
1311             }
1312 
1313             if ((cazeIf != null) && ifMatches)
1314             {
1315                 fourthCase = caze;
1316                 //return caze;
1317             }
1318         }
1319         
1320         if (firstCaseIf != null)
1321         {
1322             return firstCaseIf;
1323         }
1324         else if (firstCase != null)
1325         {
1326             return firstCase;
1327         }
1328         else if (secondCaseIf != null)
1329         {
1330             return secondCaseIf;
1331         }
1332         else if (secondCase != null)
1333         {
1334             return secondCase;
1335         }
1336         else if (thirdCaseIf != null)
1337         {
1338             return thirdCaseIf;
1339         }
1340         else if (thirdCase != null)
1341         {
1342             return thirdCase;
1343         }
1344         else if (fourthCaseIf != null)
1345         {
1346             return fourthCaseIf;
1347         }
1348         else if (fourthCase != null)
1349         {
1350             return fourthCase;
1351         }
1352         
1353         return noConditionCase;
1354     }
1355 
1356     private List<_WildcardPattern> getSortedWildcardPatterns()
1357     {
1358         return _wildcardPatterns;
1359     }
1360 
1361     @Override
1362     public Map<String, Set<NavigationCase>> getNavigationCases()
1363     {
1364         if (_developmentStage == null)
1365         {
1366             _developmentStage = FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development);
1367         }
1368         if (!Boolean.TRUE.equals(_developmentStage))
1369         {
1370             if (_navigationCases == null)
1371             {
1372                 FacesContext facesContext = FacesContext.getCurrentInstance();
1373                 ExternalContext externalContext = facesContext.getExternalContext();
1374                 RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
1375                 
1376                 calculateNavigationCases(runtimeConfig);
1377             }
1378             return _navigationCases;
1379         }
1380         else
1381         {
1382             FacesContext facesContext = FacesContext.getCurrentInstance();
1383             ExternalContext externalContext = facesContext.getExternalContext();
1384             RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
1385 
1386             if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
1387             {
1388                 calculateNavigationCases(runtimeConfig);
1389             }
1390             return _navigationCases;
1391         }
1392     }
1393 
1394     @Override
1395     public void inspectFlow(FacesContext context, Flow flow)
1396     {
1397         Map<String, Set<NavigationCase>> rules = flow.getNavigationCases();
1398         int rulesSize = rules.size();
1399 
1400         Map<String, Set<NavigationCase>> cases = new HashMap<String, Set<NavigationCase>>(
1401                 HashMapUtils.calcCapacity(rulesSize));
1402 
1403         List<_WildcardPattern> wildcardPatterns = new ArrayList<_WildcardPattern>();
1404 
1405         for (Map.Entry<String, Set<NavigationCase>> entry : rules.entrySet())
1406         {
1407             String fromViewId = entry.getKey();
1408 
1409             //specification 7.4.2 footnote 4 - missing fromViewId is allowed:
1410             if (fromViewId == null)
1411             {
1412                 fromViewId = ASTERISK;
1413             }
1414             else
1415             {
1416                 fromViewId = fromViewId.trim();
1417             }
1418 
1419             Set<NavigationCase> set = cases.get(fromViewId);
1420             if (set == null)
1421             {
1422                 set = new HashSet<NavigationCase>(entry.getValue());
1423                 cases.put(fromViewId, set);
1424                 if (fromViewId.endsWith(ASTERISK))
1425                 {
1426                     wildcardPatterns.add(new _WildcardPattern(fromViewId));
1427                 }
1428             }
1429             else
1430             {
1431                 set.addAll(entry.getValue());
1432             }
1433         }
1434 
1435         Collections.sort(wildcardPatterns, new KeyComparator());
1436 
1437         _flowNavigationStructureMap.put(
1438             flow.getId(), 
1439             new _FlowNavigationStructure(flow.getDefiningDocumentId(), flow.getId(), cases, wildcardPatterns) );
1440     }
1441     
1442     private synchronized void calculateNavigationCases(RuntimeConfig runtimeConfig)
1443     {
1444         if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
1445         {
1446             Collection<? extends NavigationRule> rules = runtimeConfig.getNavigationRules();
1447             int rulesSize = rules.size();
1448 
1449             Map<String, Set<NavigationCase>> cases = new HashMap<String, Set<NavigationCase>>(
1450                     HashMapUtils.calcCapacity(rulesSize));
1451 
1452             List<_WildcardPattern> wildcardPatterns = new ArrayList<_WildcardPattern>();
1453 
1454             for (NavigationRule rule : rules)
1455             {
1456                 String fromViewId = rule.getFromViewId();
1457 
1458                 //specification 7.4.2 footnote 4 - missing fromViewId is allowed:
1459                 if (fromViewId == null)
1460                 {
1461                     fromViewId = ASTERISK;
1462                 }
1463                 else
1464                 {
1465                     fromViewId = fromViewId.trim();
1466                 }
1467 
1468                 Set<NavigationCase> set = cases.get(fromViewId);
1469                 if (set == null)
1470                 {
1471                     set = new HashSet<NavigationCase>(convertNavigationCasesToAPI(rule));
1472                     cases.put(fromViewId, set);
1473                     if (fromViewId.endsWith(ASTERISK))
1474                     {
1475                         wildcardPatterns.add(new _WildcardPattern(fromViewId));
1476                     }
1477                 }
1478                 else
1479                 {
1480                     set.addAll(convertNavigationCasesToAPI(rule));
1481                 }
1482             }
1483 
1484             Collections.sort(wildcardPatterns, new KeyComparator());
1485 
1486             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                 _navigationCases = cases;
1493                 _wildcardPatterns = wildcardPatterns;
1494 
1495                 runtimeConfig.setNavigationRulesChanged(false);
1496             }
1497         }
1498     }
1499 
1500     private static final class KeyComparator implements Comparator<_WildcardPattern>
1501     {
1502         public int compare(_WildcardPattern s1, _WildcardPattern s2)
1503         {
1504             return -s1.getPattern().compareTo(s2.getPattern());
1505         }
1506     }
1507     
1508     private Set<NavigationCase> convertNavigationCasesToAPI(NavigationRule rule)
1509     {
1510         Collection<? extends org.apache.myfaces.config.element.NavigationCase> configCases = rule.getNavigationCases();
1511         Set<NavigationCase> apiCases = new HashSet<NavigationCase>(configCases.size());
1512         
1513         for(org.apache.myfaces.config.element.NavigationCase configCase : configCases)
1514         {   
1515             if(configCase.getRedirect() != null)
1516             {
1517                 String includeViewParamsAttribute = configCase.getRedirect().getIncludeViewParams();
1518                 boolean includeViewParams = false; // default value is false
1519                 if (includeViewParamsAttribute != null)
1520                 {
1521                     includeViewParams = Boolean.valueOf(includeViewParamsAttribute);
1522                 }
1523                 apiCases.add(new NavigationCase(rule.getFromViewId(),configCase.getFromAction(),
1524                                                 configCase.getFromOutcome(),configCase.getIf(),configCase.getToViewId(),
1525                                                 configCase.getRedirect().getViewParams(),true,includeViewParams));
1526             }
1527             else
1528             {
1529                 apiCases.add(new NavigationCase(rule.getFromViewId(),configCase.getFromAction(),
1530                                                 configCase.getFromOutcome(),configCase.getIf(),
1531                                                 configCase.getToViewId(),null,false,false));
1532             }
1533         }
1534         
1535         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         private int returnCount = 0;
1553 
1554         public NavigationContext()
1555         {
1556         }
1557 
1558         public NavigationContext(NavigationCase navigationCase)
1559         {
1560             this.navigationCase = navigationCase;
1561         }
1562         
1563         public NavigationCase getNavigationCase()
1564         {
1565             return navigationCase;
1566         }
1567 
1568         public void setNavigationCase(NavigationCase navigationCase)
1569         {
1570             this.navigationCase = navigationCase;
1571         }
1572 
1573         public List<Flow> getSourceFlows()
1574         {
1575             return sourceFlows;
1576         }
1577 
1578         public List<Flow> getTargetFlows()
1579         {
1580             return targetFlows;
1581         }
1582         
1583         public List<FlowCallNode> getFlowCallNodes()
1584         {
1585             return targetFlowCallNodes;
1586         }
1587 
1588         public void addTargetFlow(Flow sourceFlow, Flow targetFlow, FlowCallNode flowCallNode)
1589         {
1590             if (targetFlows == null)
1591             {
1592                 sourceFlows = new ArrayList<Flow>(4);
1593                 targetFlows = new ArrayList<Flow>(4);
1594                 targetFlowCallNodes = new ArrayList<FlowCallNode>(4);
1595             }
1596             this.sourceFlows.add(sourceFlow);
1597             this.targetFlows.add(targetFlow);
1598             this.targetFlowCallNodes.add(flowCallNode);
1599         }
1600         
1601         public Flow getCurrentFlow(FacesContext facesContext)
1602         {
1603             if (currentFlows != null && !currentFlows.isEmpty())
1604             {
1605                 return currentFlows.get(currentFlows.size()-1);
1606             }
1607             else
1608             {
1609                 FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
1610                 return flowHandler.getCurrentFlow(facesContext);
1611             }
1612         }
1613         
1614         public void finish(FacesContext facesContext)
1615         {
1616             // Get back flowHandler to its original state
1617             for (int i=0; i < returnCount; i++)
1618             {
1619                 FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
1620                 flowHandler.popReturnMode(facesContext);
1621             }
1622             returnCount = 0;
1623         }
1624         
1625         public void popFlow(FacesContext facesContext)
1626         {
1627             if (currentFlows != null && !currentFlows.isEmpty())
1628             {
1629                 currentFlows.remove(currentFlows.size()-1);
1630             }
1631             else
1632             {
1633                 FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
1634                 flowHandler.pushReturnMode(facesContext);
1635                 returnCount++;
1636             }
1637         }
1638         
1639         public void pushFlow(FacesContext facesContext, Flow flow)
1640         {
1641             if (currentFlows == null)
1642             {
1643                 currentFlows = new ArrayList<Flow>();
1644             }
1645             currentFlows.add(flow);
1646         }
1647         
1648         public String getLastDisplayedViewId(FacesContext facesContext, Flow flow)
1649         {
1650             FlowHandler flowHandler = facesContext.getApplication().getFlowHandler();
1651             return flowHandler.getLastDisplayedViewId(facesContext);
1652         }
1653     }
1654 }