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