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
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
106
107
108
109
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
123 UIViewRoot viewRoot = facesContext.getViewRoot();
124 if (viewRoot != null && !toViewId.equals(viewRoot.getViewId()))
125 {
126
127 Map<String, Object> viewMap = viewRoot.getViewMap(false);
128 if (viewMap != null)
129 {
130 viewMap.clear();
131 }
132 }
133
134
135
136
137
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
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
163 String newViewId = navigationCase.getToViewId(facesContext);
164
165
166
167
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
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
209
210
211
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
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
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
261 navigationCase = calcMatchingNavigationCase(facesContext, casesSet, fromAction, outcome);
262 }
263 }
264
265 if (navigationCase == null)
266 {
267
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
306
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;
319
320 }
321
322
323
324
325
326
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
338 StringBuilder viewIdToTest = SharedStringBuilder.get(facesContext, OUTCOME_NAVIGATION_SB);
339 viewIdToTest.append(outcome);
340
341
342 index = viewIdToTest.indexOf ("?");
343 if (index != -1)
344 {
345 queryString = viewIdToTest.substring (index + 1);
346
347 viewIdToTest.setLength(index);
348
349
350 if (queryString.indexOf ("faces-redirect=true") != -1)
351 {
352 isRedirect = true;
353 }
354
355
356
357 if (queryString.indexOf("includeViewParams=true") != -1
358 || queryString.indexOf("faces-include-view-params=true") != -1)
359 {
360 includeViewParams = true;
361 }
362 }
363
364
365 index = viewIdToTest.indexOf (".");
366 if (index == -1 && viewId != null)
367 {
368 index = viewId.lastIndexOf(".");
369
370 if (index != -1)
371 {
372
373 viewIdToTest.append(viewId.substring (index));
374 }
375 }
376
377
378
379
380
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
393 viewIdToTest.insert(0,"/");
394 }
395
396 else
397 {
398
399 viewIdToTest.insert(0, viewId, 0, index + 1);
400 }
401 }
402
403
404
405 try
406 {
407 implicitViewId = facesContext.getApplication().getViewHandler().deriveViewId (
408 facesContext, viewIdToTest.toString());
409 }
410
411 catch (UnsupportedOperationException e)
412 {
413
414
415
416
417 }
418
419 if (implicitViewId != null)
420 {
421
422
423 Map<String, List<String>> params = null;
424 if (queryString != null && !"".equals(queryString))
425 {
426
427 String[] splitQueryParams = AMP_PATTERN.split(queryString);
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
436 if ("includeViewParams".equals(splitParam[0])
437 || "faces-include-view-params".equals(splitParam[0])
438 || "faces-redirect".equals(splitParam[0]))
439 {
440
441 continue;
442 }
443 List<String> paramValues = params.get(splitParam[0]);
444 if (paramValues == null)
445 {
446
447 paramValues = new ArrayList<String>();
448 params.put(splitParam[0], paramValues);
449 }
450 paramValues.add(splitParam[1]);
451 }
452 else
453 {
454
455 throw new FacesException("Invalid parameter \"" +
456 queryParam + "\" in outcome " + outcome);
457 }
458 }
459 }
460
461
462 result = new NavigationCase (viewId, fromAction, outcome, null,
463 implicitViewId, params, isRedirect, includeViewParams);
464 }
465
466 return result;
467 }
468
469
470
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
479
480
481
482
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
511
512
513 if(outcome == null && (cazeOutcome != null || cazeIf == null) && actionRef == null)
514 {
515
516 continue;
517 }
518
519
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
534
535
536 if (cazeIf != null)
537 {
538 if (ifMatches)
539 {
540 firstCaseIf = caze;
541
542 }
543
544 continue;
545 }
546 else
547 {
548 firstCase = caze;
549
550 }
551 }
552 }
553 else
554 {
555 if ((actionRef != null) && cazeActionRef.equals (actionRef))
556 {
557
558
559
560 if (cazeIf != null)
561 {
562 if (ifMatches)
563 {
564 thirdCaseIf = caze;
565
566 }
567
568 continue;
569 }
570 else
571 {
572 if (outcome != null)
573 {
574 thirdCase = caze;
575
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
590
591
592 if (cazeIf != null)
593 {
594 if (ifMatches)
595 {
596 secondCaseIf = caze;
597
598 }
599
600 continue;
601 }
602 else
603 {
604 secondCase = caze;
605
606 }
607 }
608 }
609 }
610
611
612
613 if (outcome != null)
614 {
615
616 if (cazeIf != null)
617 {
618 if (ifMatches)
619 {
620 fourthCaseIf = caze;
621
622 }
623
624 continue;
625 }
626 }
627
628 if ((cazeIf != null) && ifMatches)
629 {
630 fourthCase = caze;
631
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
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
737
738
739
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;
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 }