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