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 }
635 }
636 else
637 {
638 if (cazeOutcome != null)
639 {
640 if ((outcome != null) && cazeOutcome.equals (outcome))
641 {
642
643
644
645 if (cazeIf != null)
646 {
647 if (ifMatches)
648 {
649 secondCaseIf = caze;
650
651 }
652
653 continue;
654 }
655 else
656 {
657 secondCase = caze;
658
659 }
660 }
661 }
662 }
663
664
665
666 if (outcome != null)
667 {
668
669 if (cazeIf != null)
670 {
671 if (ifMatches)
672 {
673 fourthCaseIf = caze;
674
675 }
676
677 continue;
678 }
679 }
680
681 if ((cazeIf != null) && ifMatches)
682 {
683 fourthCase = caze;
684
685 }
686 }
687
688 if (firstCaseIf != null)
689 {
690 return firstCaseIf;
691 }
692 else if (firstCase != null)
693 {
694 return firstCase;
695 }
696 else if (secondCaseIf != null)
697 {
698 return secondCaseIf;
699 }
700 else if (secondCase != null)
701 {
702 return secondCase;
703 }
704 else if (thirdCaseIf != null)
705 {
706 return thirdCaseIf;
707 }
708 else if (thirdCase != null)
709 {
710 return thirdCase;
711 }
712 else if (fourthCaseIf != null)
713 {
714 return fourthCaseIf;
715 }
716 else if (fourthCase != null)
717 {
718 return fourthCase;
719 }
720
721 return noConditionCase;
722 }
723
724 private List<String> getSortedWildcardKeys()
725 {
726 return _wildcardKeys;
727 }
728
729 @Override
730 public Map<String, Set<NavigationCase>> getNavigationCases()
731 {
732 if (_developmentStage == null)
733 {
734 _developmentStage = FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development);
735 }
736 if (!Boolean.TRUE.equals(_developmentStage))
737 {
738 if (_navigationCases == null)
739 {
740 FacesContext facesContext = FacesContext.getCurrentInstance();
741 ExternalContext externalContext = facesContext.getExternalContext();
742 RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
743
744 calculateNavigationCases(facesContext, runtimeConfig);
745 }
746 return _navigationCases;
747 }
748 else
749 {
750 FacesContext facesContext = FacesContext.getCurrentInstance();
751 ExternalContext externalContext = facesContext.getExternalContext();
752 RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
753
754 if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
755 {
756 calculateNavigationCases(facesContext, runtimeConfig);
757 }
758 return _navigationCases;
759 }
760 }
761
762 private synchronized void calculateNavigationCases(FacesContext facesContext, RuntimeConfig runtimeConfig)
763 {
764 if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
765 {
766 Collection<? extends NavigationRule> rules = runtimeConfig.getNavigationRules();
767 int rulesSize = rules.size();
768
769 Map<String, Set<NavigationCase>> cases = new HashMap<String, Set<NavigationCase>>(
770 HashMapUtils.calcCapacity(rulesSize));
771
772 List<String> wildcardKeys = new ArrayList<String>();
773
774 for (NavigationRule rule : rules)
775 {
776 String fromViewId = rule.getFromViewId();
777
778
779 if (fromViewId == null)
780 {
781 fromViewId = ASTERISK;
782 }
783 else
784 {
785 fromViewId = fromViewId.trim();
786 }
787
788 Set<NavigationCase> set = cases.get(fromViewId);
789 if (set == null)
790 {
791 set = new HashSet<NavigationCase>(convertNavigationCasesToAPI(rule));
792 cases.put(fromViewId, set);
793 if (fromViewId.endsWith(ASTERISK))
794 {
795 wildcardKeys.add(fromViewId);
796 }
797 }
798 else
799 {
800 set.addAll(convertNavigationCasesToAPI(rule));
801 }
802 }
803
804 Collections.sort(wildcardKeys, new KeyComparator());
805
806 synchronized (cases)
807 {
808
809
810
811
812 _navigationCases = cases;
813 _wildcardKeys = wildcardKeys;
814
815 runtimeConfig.setNavigationRulesChanged(false);
816 }
817 }
818 }
819
820 private static final class KeyComparator implements Comparator<String>
821 {
822 public int compare(String s1, String s2)
823 {
824 return -s1.compareTo(s2);
825 }
826 }
827
828 private Set<NavigationCase> convertNavigationCasesToAPI(NavigationRule rule)
829 {
830 Collection<? extends org.apache.myfaces.config.element.NavigationCase> configCases = rule.getNavigationCases();
831 Set<NavigationCase> apiCases = new HashSet<NavigationCase>(configCases.size());
832
833 for(org.apache.myfaces.config.element.NavigationCase configCase : configCases)
834 {
835 if(configCase.getRedirect() != null)
836 {
837 String includeViewParamsAttribute = configCase.getRedirect().getIncludeViewParams();
838 boolean includeViewParams = false;
839 if (includeViewParamsAttribute != null)
840 {
841 includeViewParams = new Boolean(includeViewParamsAttribute);
842 }
843 apiCases.add(new NavigationCase(rule.getFromViewId(),configCase.getFromAction(),
844 configCase.getFromOutcome(),configCase.getIf(),configCase.getToViewId(),
845 configCase.getRedirect().getViewParams(),true,includeViewParams));
846 }
847 else
848 {
849 apiCases.add(new NavigationCase(rule.getFromViewId(),configCase.getFromAction(),
850 configCase.getFromOutcome(),configCase.getIf(),
851 configCase.getToViewId(),null,false,false));
852 }
853 }
854
855 return apiCases;
856 }
857
858 }