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