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