1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
62
63
64
65 public class NavigationHandlerImpl
66 extends ConfigurableNavigationHandler
67 {
68
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;)?");
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
109
110
111
112
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
126 UIViewRoot viewRoot = facesContext.getViewRoot();
127 if (viewRoot != null && !toViewId.equals(viewRoot.getViewId()))
128 {
129
130 Map<String, Object> viewMap = viewRoot.getViewMap(false);
131 if (viewMap != null)
132 {
133 viewMap.clear();
134 }
135 }
136
137
138
139
140
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
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
167 String newViewId = navigationCase.getToViewId(facesContext);
168
169
170
171
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
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
214
215
216
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
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
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
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
283 navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
284 }
285 }
286
287 if (navigationCase == null)
288 {
289
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
328
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;
341
342 }
343
344
345
346
347
348
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
360 StringBuilder viewIdToTest = SharedStringBuilder.get(facesContext, OUTCOME_NAVIGATION_SB);
361 viewIdToTest.append(outcome);
362
363
364 index = viewIdToTest.indexOf ("?");
365 if (index != -1)
366 {
367 queryString = viewIdToTest.substring (index + 1);
368
369 viewIdToTest.setLength(index);
370
371
372 if (queryString.indexOf ("faces-redirect=true") != -1)
373 {
374 isRedirect = true;
375 }
376
377
378
379 if (queryString.indexOf("includeViewParams=true") != -1
380 || queryString.indexOf("faces-include-view-params=true") != -1)
381 {
382 includeViewParams = true;
383 }
384 }
385
386
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
397 viewIdToTest.append(viewId.substring (index));
398 }
399 }
400 else
401 {
402
403
404
405
406
407
408
409
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
427
428
429
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
446 viewIdToTest.insert(0,"/");
447 }
448
449 else
450 {
451
452 viewIdToTest.insert(0, viewId, 0, index + 1);
453 }
454 }
455
456
457
458 try
459 {
460 implicitViewId = facesContext.getApplication().getViewHandler().deriveViewId (
461 facesContext, viewIdToTest.toString());
462 }
463
464 catch (UnsupportedOperationException e)
465 {
466
467
468
469
470 }
471
472 if (implicitViewId != null)
473 {
474
475
476 Map<String, List<String>> params = null;
477 if (queryString != null && !"".equals(queryString))
478 {
479
480 String[] splitQueryParams = AMP_PATTERN.split(queryString);
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
489 if ("includeViewParams".equals(splitParam[0])
490 || "faces-include-view-params".equals(splitParam[0])
491 || "faces-redirect".equals(splitParam[0]))
492 {
493
494 continue;
495 }
496 List<String> paramValues = params.get(splitParam[0]);
497 if (paramValues == null)
498 {
499
500 paramValues = new ArrayList<String>();
501 params.put(splitParam[0], paramValues);
502 }
503 paramValues.add(splitParam[1]);
504 }
505 else
506 {
507
508 throw new FacesException("Invalid parameter \"" +
509 queryParam + "\" in outcome " + outcome);
510 }
511 }
512 }
513
514
515 result = new NavigationCase (viewId, fromAction, outcome, null,
516 implicitViewId, params, isRedirect, includeViewParams);
517 }
518
519 return result;
520 }
521
522
523
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
532
533
534
535
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
564
565
566 if(outcome == null && (cazeOutcome != null || cazeIf == null) && actionRef == null)
567 {
568
569 continue;
570 }
571
572
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
587
588
589 if (cazeIf != null)
590 {
591 if (ifMatches)
592 {
593 firstCaseIf = caze;
594
595 }
596
597 continue;
598 }
599 else
600 {
601 firstCase = caze;
602
603 }
604 }
605 }
606 else
607 {
608 if ((actionRef != null) && cazeActionRef.equals (actionRef))
609 {
610
611
612
613 if (cazeIf != null)
614 {
615 if (ifMatches)
616 {
617 thirdCaseIf = caze;
618
619 }
620
621 continue;
622 }
623 else
624 {
625 if (outcome != null)
626 {
627 thirdCase = caze;
628
629 }
630
631 continue;
632 }
633 }
634 else
635 {
636
637
638
639
640 continue;
641 }
642 }
643 }
644 else
645 {
646 if (cazeOutcome != null)
647 {
648 if ((outcome != null) && cazeOutcome.equals (outcome))
649 {
650
651
652
653 if (cazeIf != null)
654 {
655 if (ifMatches)
656 {
657 secondCaseIf = caze;
658
659 }
660
661 continue;
662 }
663 else
664 {
665 secondCase = caze;
666
667 }
668 }
669 }
670 }
671
672
673
674 if (outcome != null)
675 {
676
677 if (cazeIf != null)
678 {
679 if (ifMatches)
680 {
681 fourthCaseIf = caze;
682
683 }
684
685 continue;
686 }
687 }
688
689 if ((cazeIf != null) && ifMatches)
690 {
691 fourthCase = caze;
692
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
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
817
818
819
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;
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 }