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.logging.Level;
33  import java.util.logging.Logger;
34  
35  import java.util.regex.Pattern;
36  import javax.faces.FacesException;
37  import javax.faces.application.ConfigurableNavigationHandler;
38  import javax.faces.application.FacesMessage;
39  import javax.faces.application.NavigationCase;
40  import javax.faces.application.ProjectStage;
41  import javax.faces.application.ViewHandler;
42  import javax.faces.component.UIComponent;
43  import javax.faces.component.UIViewRoot;
44  import javax.faces.component.visit.VisitCallback;
45  import javax.faces.component.visit.VisitContext;
46  import javax.faces.component.visit.VisitHint;
47  import javax.faces.component.visit.VisitResult;
48  import javax.faces.context.ExternalContext;
49  import javax.faces.context.FacesContext;
50  import javax.faces.context.PartialViewContext;
51  import javax.faces.view.ViewDeclarationLanguage;
52  import javax.faces.view.ViewMetadata;
53  
54  import org.apache.myfaces.config.RuntimeConfig;
55  import org.apache.myfaces.config.element.NavigationRule;
56  import org.apache.myfaces.shared.application.NavigationUtils;
57  import org.apache.myfaces.shared.renderkit.html.util.SharedStringBuilder;
58  import org.apache.myfaces.shared.util.HashMapUtils;
59  import org.apache.myfaces.shared.util.StringUtils;
60  import org.apache.myfaces.view.facelets.tag.jsf.PreDisposeViewEvent;
61  
62  /**
63   * @author Thomas Spiegl (latest modification by $Author: lu4242 $)
64   * @author Anton Koinov
65   * @version $Revision: 1669632 $ $Date: 2015-03-27 17:49:47 +0000 (Fri, 27 Mar 2015) $
66   */
67  public class NavigationHandlerImpl
68      extends ConfigurableNavigationHandler
69  {
70      //private static final Log log = LogFactory.getLog(NavigationHandlerImpl.class);
71      private static final Logger log = Logger.getLogger(NavigationHandlerImpl.class.getName());
72  
73      private static final String SKIP_ITERATION_HINT = "javax.faces.visit.SKIP_ITERATION";
74      
75      private static final Set<VisitHint> VISIT_HINTS = Collections.unmodifiableSet(
76              EnumSet.of(VisitHint.SKIP_ITERATION));    
77      
78      private static final String OUTCOME_NAVIGATION_SB = "oam.navigation.OUTCOME_NAVIGATION_SB";
79      
80      private static final Pattern AMP_PATTERN = Pattern.compile("&(amp;)?"); // "&" or "&amp;"
81      
82      private static final String ASTERISK = "*";
83  
84      private Map<String, Set<NavigationCase>> _navigationCases = null;
85      private List<String> _wildcardKeys = new ArrayList<String>();
86      private Boolean _developmentStage;
87      
88      private NavigationHandlerSupport navigationHandlerSupport;
89  
90      public NavigationHandlerImpl()
91      {
92          if (log.isLoggable(Level.FINEST))
93          {
94              log.finest("New NavigationHandler instance created");
95          }
96      }
97  
98      @Override
99      public void handleNavigation(FacesContext facesContext, String fromAction, String outcome)
100     {
101         NavigationCase navigationCase = getNavigationCase(facesContext, fromAction, outcome);
102 
103         if (navigationCase != null)
104         {
105             if (log.isLoggable(Level.FINEST))
106             {
107                 log.finest("handleNavigation fromAction=" + fromAction + " outcome=" + outcome +
108                           " toViewId =" + navigationCase.getToViewId(facesContext) +
109                           " redirect=" + navigationCase.isRedirect());
110             }
111             if (navigationCase.isRedirect())
112             { 
113                 //&& (!PortletUtil.isPortletRequest(facesContext)))
114                 // Spec section 7.4.2 says "redirects not possible" in this case for portlets
115                 //But since the introduction of portlet bridge and the 
116                 //removal of portlet code in myfaces core 2.0, this condition
117                 //no longer applies
118                 
119                 ExternalContext externalContext = facesContext.getExternalContext();
120                 ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
121                 String toViewId = navigationCase.getToViewId(facesContext);
122                 
123 
124                 String redirectPath = viewHandler.getRedirectURL(
125                         facesContext, toViewId, 
126                         NavigationUtils.getEvaluatedNavigationParameters(facesContext,
127                         navigationCase.getParameters()) ,
128                         navigationCase.isIncludeViewParams());
129                 
130                 //Clear ViewMap if we are redirecting to other resource
131                 UIViewRoot viewRoot = facesContext.getViewRoot(); 
132                 if (viewRoot != null && !toViewId.equals(viewRoot.getViewId()))
133                 {
134                     //call getViewMap(false) to prevent unnecessary map creation
135                     Map<String, Object> viewMap = viewRoot.getViewMap(false);
136                     if (viewMap != null)
137                     {
138                         viewMap.clear();
139                     }
140                 }
141                 
142                 // JSF 2.0 the javadoc of handleNavigation() says something like this 
143                 // "...If the view has changed after an application action, call
144                 // PartialViewContext.setRenderAll(true)...". The effect is that ajax requests
145                 // are included on navigation.
146                 PartialViewContext partialViewContext = facesContext.getPartialViewContext();
147                 String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
148                 if ( partialViewContext.isPartialRequest() && 
149                      !partialViewContext.isRenderAll() && 
150                      toViewId != null &&
151                      !toViewId.equals(viewId))
152                 {
153                     partialViewContext.setRenderAll(true);
154                 }
155                 
156                 // JSF 2.0 Spec call Flash.setRedirect(true) to notify Flash scope and take proper actions
157                 externalContext.getFlash().setRedirect(true);
158                 try
159                 {
160                     externalContext.redirect(redirectPath);
161                     facesContext.responseComplete();
162                 }
163                 catch (IOException e)
164                 {
165                     throw new FacesException(e.getMessage(), e);
166                 }
167             }
168             else
169             {
170                 ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
171                 //create new view
172                 String newViewId = navigationCase.getToViewId(facesContext);
173                 // JSF 2.0 the javadoc of handleNavigation() says something like this 
174                 // "...If the view has changed after an application action, call
175                 // PartialViewContext.setRenderAll(true)...". The effect is that ajax requests
176                 // are included on navigation.
177                 PartialViewContext partialViewContext = facesContext.getPartialViewContext();
178                 String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
179                 if ( partialViewContext.isPartialRequest() && 
180                      !partialViewContext.isRenderAll() && 
181                      newViewId != null &&
182                      !newViewId.equals(viewId))
183                 {
184                     partialViewContext.setRenderAll(true);
185                 }
186 
187                 if (facesContext.getViewRoot() != null &&
188                     facesContext.getViewRoot().getAttributes().containsKey("oam.CALL_PRE_DISPOSE_VIEW"))
189                 {
190                     try
191                     {
192                         facesContext.getAttributes().put(SKIP_ITERATION_HINT, Boolean.TRUE);
193 
194                         VisitContext visitContext = VisitContext.createVisitContext(facesContext, null, VISIT_HINTS);
195                         facesContext.getViewRoot().visitTree(visitContext,
196                                                              new PreDisposeViewCallback());
197                     }
198                     finally
199                     {
200                         facesContext.getAttributes().remove(SKIP_ITERATION_HINT);
201                     }
202                 }
203 
204                 // create UIViewRoot for new view
205                 UIViewRoot viewRoot = null;
206                 
207                 String derivedViewId = viewHandler.deriveViewId(facesContext, newViewId);
208 
209                 if (derivedViewId != null)
210                 {
211                     ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(facesContext, derivedViewId);
212                     
213                     if (vdl != null)
214                     {
215                         ViewMetadata metadata = vdl.getViewMetadata(facesContext, newViewId);
216                         
217                         if (metadata != null)
218                         {
219                             viewRoot = metadata.createMetadataView(facesContext);
220                         }
221                     }
222                 }
223                 
224                 // viewRoot can be null here, if ...
225                 //   - we don't have a ViewDeclarationLanguage (e.g. when using facelets-1.x)
226                 //   - there is no view metadata or metadata.createMetadataView() returned null
227                 //   - viewHandler.deriveViewId() returned null
228                 if (viewRoot == null)
229                 {
230                     viewRoot = viewHandler.createView(facesContext, newViewId);
231                 }
232                 
233                 facesContext.setViewRoot(viewRoot);
234                 facesContext.renderResponse();
235             }
236         }
237         else
238         {
239             // no navigationcase found, stay on current ViewRoot
240             if (log.isLoggable(Level.FINEST))
241             {
242                 log.finest("handleNavigation fromAction=" + fromAction + " outcome=" + outcome +
243                           " no matching navigation-case found, staying on current ViewRoot");
244             }
245         }
246     }
247 
248     /**
249     * @return the navigationHandlerSupport
250     */
251     protected NavigationHandlerSupport getNavigationHandlerSupport()
252     {
253         if (navigationHandlerSupport == null)
254         {
255             navigationHandlerSupport = new DefaultNavigationHandlerSupport();
256         }
257         return navigationHandlerSupport;
258     }
259 
260     public void setNavigationHandlerSupport(NavigationHandlerSupport navigationHandlerSupport)
261     {
262         this.navigationHandlerSupport = navigationHandlerSupport;
263     }
264 
265     private static class PreDisposeViewCallback implements VisitCallback
266     {
267 
268         public VisitResult visit(VisitContext context, UIComponent target)
269         {
270             context.getFacesContext().getApplication().publishEvent(context.getFacesContext(),
271                                                                     PreDisposeViewEvent.class, target);
272             
273             return VisitResult.ACCEPT;
274         }
275     }
276 
277     /**
278      * Returns the navigation case that applies for the given action and outcome
279      */
280     public NavigationCase getNavigationCase(FacesContext facesContext, String fromAction, String outcome)
281     {
282         String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
283         
284         Map<String, Set<NavigationCase>> casesMap = getNavigationCases();
285         NavigationCase navigationCase = null;
286         
287         Set<? extends NavigationCase> casesSet;
288         if (viewId != null)
289         {
290             casesSet = casesMap.get(viewId);
291             if (casesSet != null)
292             {
293                 // Exact match?
294                 navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
295             }
296         }
297 
298         if (navigationCase == null)
299         {
300             // Wildcard match?
301             List<String> sortedWildcardKeys = getSortedWildcardKeys();
302             for (int i = 0; i < sortedWildcardKeys.size(); i++)
303             {
304                 String fromViewId = sortedWildcardKeys.get(i);
305                 if (fromViewId.length() > 2)
306                 {
307                     String prefix = fromViewId.substring(0, fromViewId.length() - 1);
308                     if (viewId != null && viewId.startsWith(prefix))
309                     {
310                         casesSet = casesMap.get(fromViewId);
311                         if (casesSet != null)
312                         {
313                             navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
314                             if (navigationCase != null)
315                             {
316                                 break;
317                             }
318                         }
319                     }
320                 }
321                 else
322                 {
323                     casesSet = casesMap.get(fromViewId);
324                     if (casesSet != null)
325                     {
326                         navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
327                         if (navigationCase != null)
328                         {
329                             break;
330                         }
331                     }
332                 }
333             }
334         }
335         
336         if (outcome != null && navigationCase == null)
337         {
338             //if outcome is null, we don't check outcome based nav cases
339             //otherwise, if navgiationCase is still null, check outcome-based nav cases
340             navigationCase = getOutcomeNavigationCase (facesContext, fromAction, outcome);
341         }
342         
343         if (outcome != null && navigationCase == null && !facesContext.isProjectStage(ProjectStage.Production))
344         {
345             final FacesMessage facesMessage = new FacesMessage("No navigation case match for viewId " + viewId + 
346                     ",  action " + fromAction + " and outcome " + outcome);
347             facesMessage.setSeverity(FacesMessage.SEVERITY_WARN);
348             facesContext.addMessage(null, facesMessage);
349         }
350 
351         return navigationCase;  //if navigationCase == null, will stay on current view
352 
353     }
354     
355     /**
356      * Performs the algorithm specified in 7.4.2 for situations where no navigation cases are defined and instead
357      * the navigation case is to be determined from the outcome.
358      * 
359      * TODO: cache results?
360      */
361     private NavigationCase getOutcomeNavigationCase (FacesContext facesContext, String fromAction, String outcome)
362     {
363         String implicitViewId = null;
364         boolean includeViewParams = false;
365         int index;
366         boolean isRedirect = false;
367         String queryString = null;
368         NavigationCase result = null;
369         String viewId = facesContext.getViewRoot() != null ? facesContext.getViewRoot().getViewId() : null;
370         //String viewIdToTest = outcome;
371         StringBuilder viewIdToTest = SharedStringBuilder.get(facesContext, OUTCOME_NAVIGATION_SB);
372         viewIdToTest.append(outcome);
373         
374         // If viewIdToTest contains a query string, remove it and set queryString with that value.
375         index = viewIdToTest.indexOf ("?");
376         if (index != -1)
377         {
378             queryString = viewIdToTest.substring (index + 1);
379             //viewIdToTest = viewIdToTest.substring (0, index);
380             viewIdToTest.setLength(index);
381             
382             // If queryString contains "faces-redirect=true", set isRedirect to true.
383             if (queryString.indexOf ("faces-redirect=true") != -1)
384             {
385                 isRedirect = true;
386             }
387             
388             // If queryString contains "includeViewParams=true" or 
389             // "faces-include-view-params=true", set includeViewParams to true.
390             if (queryString.indexOf("includeViewParams=true") != -1 
391                     || queryString.indexOf("faces-include-view-params=true") != -1)
392             {
393                 includeViewParams = true;
394             }
395         }
396         
397         // If viewIdToTest does not have a "file extension", use the one from the current viewId.
398         index = viewIdToTest.indexOf (".");
399         if (index == -1)
400         {
401             if (viewId != null)
402             {
403                 index = viewId.lastIndexOf(".");
404 
405                 if (index != -1)
406                 {
407                     //viewIdToTest += viewId.substring (index);
408                     viewIdToTest.append(viewId.substring (index));
409                 }
410             }
411             else
412             {
413                 // This case happens when for for example there is a ViewExpiredException,
414                 // and a custom ExceptionHandler try to navigate using implicit navigation.
415                 // In this case, there is no UIViewRoot set on the FacesContext, so viewId 
416                 // is null.
417 
418                 // In this case, it should try to derive the viewId of the view that was
419                 // not able to restore, to get the extension and apply it to
420                 // the implicit navigation.
421                 String tempViewId = getNavigationHandlerSupport().calculateViewId(facesContext);
422                 if (tempViewId != null)
423                 {
424                     index = tempViewId.lastIndexOf(".");
425                     if(index != -1)
426                     {
427                         viewIdToTest.append(tempViewId.substring (index));
428                     }
429                 }
430             }
431             if (log.isLoggable(Level.FINEST))
432             {
433                 log.finest("getOutcomeNavigationCase -> viewIdToTest: " + viewIdToTest);
434             } 
435         }
436 
437         // If viewIdToTest does not start with "/", look for the last "/" in viewId.  If not found, simply prepend "/".
438         // Otherwise, prepend everything before and including the last "/" in viewId.
439         
440         //if (!viewIdToTest.startsWith ("/") && viewId != null)
441         boolean startWithSlash = false;
442         if (viewIdToTest.length() > 0)
443         {
444             startWithSlash = (viewIdToTest.charAt(0) == '/');
445         } 
446         if (!startWithSlash) 
447         {
448             index = -1;
449             if( viewId != null )
450             {
451                index = viewId.lastIndexOf ("/");
452             }
453             
454             if (index == -1)
455             {
456                 //viewIdToTest = "/" + viewIdToTest;
457                 viewIdToTest.insert(0,"/");
458             }
459             
460             else
461             {
462                 //viewIdToTest = viewId.substring (0, index + 1) + viewIdToTest;
463                 viewIdToTest.insert(0, viewId, 0, index + 1);
464             }
465         }
466         
467         // Call ViewHandler.deriveViewId() and set the result as implicitViewId.
468         
469         try
470         {
471             implicitViewId = facesContext.getApplication().getViewHandler().deriveViewId (
472                     facesContext, viewIdToTest.toString());
473         }
474         
475         catch (UnsupportedOperationException e)
476         {
477             // This is the case when a pre-JSF 2.0 ViewHandler is used.
478             // In this case, the default algorithm must be used.
479             // FIXME: I think we're always calling the "default" ViewHandler.deriveViewId() algorithm and we don't
480             // distinguish between pre-JSF 2.0 and JSF 2.0 ViewHandlers.  This probably needs to be addressed.
481         }
482         
483         if (implicitViewId != null)
484         {
485             // Append all params from the queryString
486             // (excluding faces-redirect, includeViewParams and faces-include-view-params)
487             Map<String, List<String>> params = null;
488             if (queryString != null && !"".equals(queryString))
489             {
490                 //String[] splitQueryParams = queryString.split("&(amp;)?"); // "&" or "&amp;"
491                 String[] splitQueryParams = AMP_PATTERN.split(queryString); // "&" or "&amp;"
492                 params = new HashMap<String, List<String>>(splitQueryParams.length, 
493                         (splitQueryParams.length* 4 + 3) / 3);
494                 for (String queryParam : splitQueryParams)
495                 {
496                     String[] splitParam = StringUtils.splitShortString(queryParam, '=');
497                     if (splitParam.length == 2)
498                     {
499                         // valid parameter - add it to params
500                         if ("includeViewParams".equals(splitParam[0])
501                                 || "faces-include-view-params".equals(splitParam[0])
502                                 || "faces-redirect".equals(splitParam[0]))
503                         {
504                             // ignore includeViewParams, faces-include-view-params and faces-redirect
505                             continue;
506                         }
507                         List<String> paramValues = params.get(splitParam[0]);
508                         if (paramValues == null)
509                         {
510                             // no value for the given parameter yet
511                             paramValues = new ArrayList<String>();
512                             params.put(splitParam[0], paramValues);
513                         }
514                         paramValues.add(splitParam[1]);
515                     }
516                     else
517                     {
518                         // invalid parameter
519                         throw new FacesException("Invalid parameter \"" + 
520                                 queryParam + "\" in outcome " + outcome);
521                     }
522                 }
523             }
524             
525             // Finally, create the NavigationCase.
526             result = new NavigationCase (viewId, fromAction, outcome, null, 
527                     implicitViewId, params, isRedirect, includeViewParams);
528         }
529         
530         return result;
531     }
532     
533     /**
534      * Returns the view ID that would be created for the given action and outcome
535      */
536     public String getViewId(FacesContext context, String fromAction, String outcome)
537     {
538         return this.getNavigationCase(context, fromAction, outcome).getToViewId(context);
539     }
540 
541     /**
542      * TODO
543      * Invoked by the navigation handler before the new view component is created.
544      * @param viewId The view ID to be created
545      * @return The view ID that should be used instead. If null, the view ID passed
546      * in will be used without modification.
547      */
548     public String beforeNavigation(String viewId)
549     {
550         return null;
551     }
552 
553     private NavigationCase calcMatchingNavigationCase(FacesContext context,
554                                                       Set<? extends NavigationCase> casesList,
555                                                       String actionRef,
556                                                       String outcome)
557     {
558         NavigationCase noConditionCase = null;
559         NavigationCase firstCase = null;
560         NavigationCase firstCaseIf = null;
561         NavigationCase secondCase = null;
562         NavigationCase secondCaseIf = null;
563         NavigationCase thirdCase = null;
564         NavigationCase thirdCaseIf = null;
565         NavigationCase fourthCase = null;
566         NavigationCase fourthCaseIf = null;
567                         
568         for (NavigationCase caze : casesList)
569         {
570             String cazeOutcome = caze.getFromOutcome();
571             String cazeActionRef = caze.getFromAction();
572             Boolean cazeIf = caze.getCondition(context);
573             boolean ifMatches = (cazeIf == null ? false : cazeIf.booleanValue());
574             // JSF 2.0: support conditional navigation via <if>.
575             // Use for later cases.
576             
577             if(outcome == null && (cazeOutcome != null || cazeIf == null) && actionRef == null)
578             {
579                 //To match an outcome value of null, the <from-outcome> must be absent and the <if> element present.
580                 continue;
581             }
582             
583             //If there are no conditions on navigation case save it and return as last resort
584             if (cazeOutcome == null && cazeActionRef == null &&
585                 cazeIf == null && noConditionCase == null && outcome != null)
586             {
587                 noConditionCase = caze;
588             }
589             
590             if (cazeActionRef != null)
591             {
592                 if (cazeOutcome != null)
593                 {
594                     if ((actionRef != null) && (outcome != null) && cazeActionRef.equals (actionRef) &&
595                             cazeOutcome.equals (outcome))
596                     {
597                         // First case: match if <from-action> matches action and <from-outcome> matches outcome.
598                         // Caveat: evaluate <if> if available.
599 
600                         if (cazeIf != null)
601                         {
602                             if (ifMatches)
603                             {
604                                 firstCaseIf = caze;
605                                 //return caze;
606                             }
607 
608                             continue;
609                         }
610                         else
611                         {
612                             firstCase = caze;
613                             //return caze;
614                         }
615                     }
616                 }
617                 else
618                 {
619                     if ((actionRef != null) && cazeActionRef.equals (actionRef))
620                     {
621                         // Third case: if only <from-action> specified, match against action.
622                         // Caveat: if <if> is available, evaluate.  If not, only match if outcome is not null.
623 
624                         if (cazeIf != null)
625                         {
626                             if (ifMatches)
627                             {
628                                 thirdCaseIf = caze;
629                                 //return caze;
630                             }
631                             
632                             continue;
633                         }
634                         else
635                         {
636                             if (outcome != null)
637                             {
638                                 thirdCase = caze;
639                                 //return caze;
640                             }
641                             
642                             continue;
643                         }
644                     }
645                     else
646                     {
647                         // cazeActionRef != null and cazeOutcome == null
648                         // but cazeActionRef does not match. No additional operation
649                         // required because cazeIf is only taken into account 
650                         // it cazeActionRef match. 
651                         continue;
652                     }
653                 }
654             }
655             else
656             {
657                 if (cazeOutcome != null)
658                 {
659                     if ((outcome != null) && cazeOutcome.equals (outcome))
660                     {
661                         // Second case: if only <from-outcome> specified, match against outcome.
662                         // Caveat: if <if> is available, evaluate.
663 
664                         if (cazeIf != null)
665                         {
666                             if (ifMatches)
667                             {
668                                 secondCaseIf = caze;
669                                 //return caze;
670                             }
671                             
672                             continue;
673                         }
674                         else
675                         {
676                             secondCase = caze;
677                             //return caze;
678                         }
679                     }
680                 }
681             }
682 
683             // Fourth case: anything else matches if outcome is not null or <if> is specified.
684 
685             if (outcome != null)
686             {
687                 // Again, if <if> present, evaluate.
688                 if (cazeIf != null)
689                 {
690                     if (ifMatches)
691                     {
692                         fourthCaseIf = caze;
693                         //return caze;
694                     }
695                     
696                     continue;
697                 }
698             }
699 
700             if ((cazeIf != null) && ifMatches)
701             {
702                 fourthCase = caze;
703                 //return caze;
704             }
705         }
706         
707         if (firstCaseIf != null)
708         {
709             return firstCaseIf;
710         }
711         else if (firstCase != null)
712         {
713             return firstCase;
714         }
715         else if (secondCaseIf != null)
716         {
717             return secondCaseIf;
718         }
719         else if (secondCase != null)
720         {
721             return secondCase;
722         }
723         else if (thirdCaseIf != null)
724         {
725             return thirdCaseIf;
726         }
727         else if (thirdCase != null)
728         {
729             return thirdCase;
730         }
731         else if (fourthCaseIf != null)
732         {
733             return fourthCaseIf;
734         }
735         else if (fourthCase != null)
736         {
737             return fourthCase;
738         }
739         
740         return noConditionCase;
741     }
742 
743     private List<String> getSortedWildcardKeys()
744     {
745         return _wildcardKeys;
746     }
747 
748     @Override
749     public Map<String, Set<NavigationCase>> getNavigationCases()
750     {
751         if (_developmentStage == null)
752         {
753             _developmentStage = FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development);
754         }
755         if (!Boolean.TRUE.equals(_developmentStage))
756         {
757             if (_navigationCases == null)
758             {
759                 FacesContext facesContext = FacesContext.getCurrentInstance();
760                 ExternalContext externalContext = facesContext.getExternalContext();
761                 RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
762                 
763                 calculateNavigationCases(facesContext, runtimeConfig);
764             }
765             return _navigationCases;
766         }
767         else
768         {
769             FacesContext facesContext = FacesContext.getCurrentInstance();
770             ExternalContext externalContext = facesContext.getExternalContext();
771             RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
772 
773             if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
774             {
775                 calculateNavigationCases(facesContext, runtimeConfig);
776             }
777             return _navigationCases;
778         }
779     }
780     
781     private synchronized void calculateNavigationCases(FacesContext facesContext, RuntimeConfig runtimeConfig)
782     {
783         if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
784         {
785             Collection<? extends NavigationRule> rules = runtimeConfig.getNavigationRules();
786             int rulesSize = rules.size();
787 
788             Map<String, Set<NavigationCase>> cases = new HashMap<String, Set<NavigationCase>>(
789                     HashMapUtils.calcCapacity(rulesSize));
790 
791             List<String> wildcardKeys = new ArrayList<String>();
792 
793             for (NavigationRule rule : rules)
794             {
795                 String fromViewId = rule.getFromViewId();
796 
797                 //specification 7.4.2 footnote 4 - missing fromViewId is allowed:
798                 if (fromViewId == null)
799                 {
800                     fromViewId = ASTERISK;
801                 }
802                 else
803                 {
804                     fromViewId = fromViewId.trim();
805                 }
806 
807                 Set<NavigationCase> set = cases.get(fromViewId);
808                 if (set == null)
809                 {
810                     set = new HashSet<NavigationCase>(convertNavigationCasesToAPI(rule));
811                     cases.put(fromViewId, set);
812                     if (fromViewId.endsWith(ASTERISK))
813                     {
814                         wildcardKeys.add(fromViewId);
815                     }
816                 }
817                 else
818                 {
819                     set.addAll(convertNavigationCasesToAPI(rule));
820                 }
821             }
822 
823             Collections.sort(wildcardKeys, new KeyComparator());
824 
825             synchronized (cases)
826             {
827                 // We do not really need this sychronization at all, but this
828                 // gives us the peace of mind that some good optimizing compiler
829                 // will not rearrange the execution of the assignment to an
830                 // earlier time, before all init code completes
831                 _navigationCases = cases;
832                 _wildcardKeys = wildcardKeys;
833 
834                 runtimeConfig.setNavigationRulesChanged(false);
835             }
836         }
837     }
838 
839     private static final class KeyComparator implements Comparator<String>
840     {
841         public int compare(String s1, String s2)
842         {
843             return -s1.compareTo(s2);
844         }
845     }
846     
847     private Set<NavigationCase> convertNavigationCasesToAPI(NavigationRule rule)
848     {
849         Collection<? extends org.apache.myfaces.config.element.NavigationCase> configCases = rule.getNavigationCases();
850         Set<NavigationCase> apiCases = new HashSet<NavigationCase>(configCases.size());
851         
852         for(org.apache.myfaces.config.element.NavigationCase configCase : configCases)
853         {   
854             if(configCase.getRedirect() != null)
855             {
856                 String includeViewParamsAttribute = configCase.getRedirect().getIncludeViewParams();
857                 boolean includeViewParams = false; // default value is false
858                 if (includeViewParamsAttribute != null)
859                 {
860                     includeViewParams = new Boolean(includeViewParamsAttribute);
861                 }
862                 apiCases.add(new NavigationCase(rule.getFromViewId(),configCase.getFromAction(),
863                                                 configCase.getFromOutcome(),configCase.getIf(),configCase.getToViewId(),
864                                                 configCase.getRedirect().getViewParams(),true,includeViewParams));
865             }
866             else
867             {
868                 apiCases.add(new NavigationCase(rule.getFromViewId(),configCase.getFromAction(),
869                                                 configCase.getFromOutcome(),configCase.getIf(),
870                                                 configCase.getToViewId(),null,false,false));
871             }
872         }
873         
874         return apiCases;
875     }
876 
877 }