1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.myfaces.lifecycle;
20
21 import java.net.MalformedURLException;
22 import java.util.Collections;
23 import java.util.EnumSet;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.logging.Level;
27 import java.util.logging.Logger;
28
29 import javax.faces.FacesException;
30 import javax.faces.FactoryFinder;
31 import javax.faces.application.ProjectStage;
32 import javax.faces.application.ViewHandler;
33 import javax.faces.component.UIComponent;
34 import javax.faces.component.visit.VisitCallback;
35 import javax.faces.component.visit.VisitContext;
36 import javax.faces.component.visit.VisitContextFactory;
37 import javax.faces.component.visit.VisitHint;
38 import javax.faces.component.visit.VisitResult;
39 import javax.faces.context.ExternalContext;
40 import javax.faces.context.FacesContext;
41 import javax.faces.event.PostRestoreStateEvent;
42 import javax.faces.render.RenderKitFactory;
43 import javax.faces.render.ResponseStateManager;
44 import javax.faces.view.ViewDeclarationLanguage;
45
46 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
47 import org.apache.myfaces.shared.application.FacesServletMapping;
48 import org.apache.myfaces.shared.application.InvalidViewIdException;
49 import org.apache.myfaces.shared.util.Assert;
50 import org.apache.myfaces.shared.util.ConcurrentLRUCache;
51 import org.apache.myfaces.shared.util.ExternalContextUtils;
52 import org.apache.myfaces.shared.util.WebConfigParamUtils;
53
54
55
56
57
58 public class DefaultRestoreViewSupport implements RestoreViewSupport
59 {
60 private static final String JAVAX_SERVLET_INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
61
62 private static final String JAVAX_SERVLET_INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
63
64
65
66
67
68 private static final String PORTLET_LIFECYCLE_PHASE = "javax.portlet.faces.phase";
69
70 private static final String CACHED_SERVLET_MAPPING =
71 DefaultRestoreViewSupport.class.getName() + ".CACHED_SERVLET_MAPPING";
72
73
74
75 private final Logger log = Logger.getLogger(DefaultRestoreViewSupport.class.getName());
76
77
78
79
80 @JSFWebConfigParam(defaultValue = "500", since = "2.0.2", group="viewhandler", tags="performance",
81 classType="java.lang.Integer",
82 desc="Controls the size of the cache used to 'remember' if a view exists or not.")
83 private static final String CHECKED_VIEWID_CACHE_SIZE_ATTRIBUTE = "org.apache.myfaces.CHECKED_VIEWID_CACHE_SIZE";
84 private static final int CHECKED_VIEWID_CACHE_DEFAULT_SIZE = 500;
85
86
87
88
89
90 @JSFWebConfigParam(defaultValue = "true", since = "2.0.2", expectedValues="true, false", group="viewhandler",
91 tags="performance",
92 desc="Enable or disable a cache used to 'remember' if a view exists or not and reduce the impact " +
93 "of sucesive calls to ExternalContext.getResource().")
94 private static final String CHECKED_VIEWID_CACHE_ENABLED_ATTRIBUTE =
95 "org.apache.myfaces.CHECKED_VIEWID_CACHE_ENABLED";
96 private static final boolean CHECKED_VIEWID_CACHE_ENABLED_DEFAULT = true;
97
98 private static final String SKIP_ITERATION_HINT = "javax.faces.visit.SKIP_ITERATION";
99
100 private static final Set<VisitHint> VISIT_HINTS = Collections.unmodifiableSet(
101 EnumSet.of(VisitHint.SKIP_ITERATION));
102
103 private volatile ConcurrentLRUCache<String, Boolean> _checkedViewIdMap = null;
104 private Boolean _checkedViewIdCacheEnabled = null;
105
106 private RenderKitFactory _renderKitFactory = null;
107 private VisitContextFactory _visitContextFactory = null;
108
109 private final String[] _faceletsViewMappings;
110 private final String[] _contextSuffixes;
111 private final String _faceletsContextSufix;
112 private final boolean _initialized;
113
114 public DefaultRestoreViewSupport()
115 {
116 _faceletsViewMappings = null;
117 _contextSuffixes = null;
118 _faceletsContextSufix = null;
119 _initialized = false;
120 }
121
122 public DefaultRestoreViewSupport(FacesContext facesContext)
123 {
124 _faceletsViewMappings = getFaceletsViewMappings(facesContext);
125 _contextSuffixes = getContextSuffix(facesContext);
126 _faceletsContextSufix = getFaceletsContextSuffix(facesContext);
127 _initialized = true;
128 }
129
130 public void processComponentBinding(FacesContext facesContext, UIComponent component)
131 {
132
133
134
135
136
137
138 try
139 {
140 facesContext.getAttributes().put(SKIP_ITERATION_HINT, Boolean.TRUE);
141
142 VisitContext visitContext = (VisitContext) getVisitContextFactory().
143 getVisitContext(facesContext, null, VISIT_HINTS);
144 component.visitTree(visitContext, new RestoreStateCallback());
145 }
146 finally
147 {
148
149
150 facesContext.getAttributes().remove(SKIP_ITERATION_HINT);
151 }
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173 }
174
175 public String calculateViewId(FacesContext facesContext)
176 {
177 Assert.notNull(facesContext);
178 ExternalContext externalContext = facesContext.getExternalContext();
179 Map<String, Object> requestMap = externalContext.getRequestMap();
180
181 String viewId = null;
182 boolean traceEnabled = log.isLoggable(Level.FINEST);
183
184 if (requestMap.containsKey(PORTLET_LIFECYCLE_PHASE))
185 {
186 viewId = (String) externalContext.getRequestPathInfo();
187 }
188 else
189 {
190 viewId = (String) requestMap.get(JAVAX_SERVLET_INCLUDE_PATH_INFO);
191 if (viewId != null)
192 {
193 if (traceEnabled)
194 {
195 log.finest("Calculated viewId '" + viewId + "' from request param '"
196 + JAVAX_SERVLET_INCLUDE_PATH_INFO + "'");
197 }
198 }
199 else
200 {
201 viewId = externalContext.getRequestPathInfo();
202 if (viewId != null && traceEnabled)
203 {
204 log.finest("Calculated viewId '" + viewId + "' from request path info");
205 }
206 }
207
208 if (viewId == null)
209 {
210 viewId = (String) requestMap.get(JAVAX_SERVLET_INCLUDE_SERVLET_PATH);
211 if (viewId != null && traceEnabled)
212 {
213 log.finest("Calculated viewId '" + viewId + "' from request param '"
214 + JAVAX_SERVLET_INCLUDE_SERVLET_PATH + "'");
215 }
216 }
217 }
218
219 if (viewId == null)
220 {
221 viewId = externalContext.getRequestServletPath();
222 if (viewId != null && traceEnabled)
223 {
224 log.finest("Calculated viewId '" + viewId + "' from request servlet path");
225 }
226 }
227
228 if (viewId == null)
229 {
230 throw new FacesException("Could not determine view id.");
231 }
232
233 return viewId;
234 }
235
236 public boolean isPostback(FacesContext facesContext)
237 {
238 ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
239 String renderkitId = viewHandler.calculateRenderKitId(facesContext);
240 ResponseStateManager rsm
241 = getRenderKitFactory().getRenderKit(facesContext, renderkitId).getResponseStateManager();
242 return rsm.isPostback(facesContext);
243 }
244
245 protected RenderKitFactory getRenderKitFactory()
246 {
247 if (_renderKitFactory == null)
248 {
249 _renderKitFactory = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
250 }
251 return _renderKitFactory;
252 }
253
254 protected VisitContextFactory getVisitContextFactory()
255 {
256 if (_visitContextFactory == null)
257 {
258 _visitContextFactory = (VisitContextFactory)FactoryFinder.getFactory(FactoryFinder.VISIT_CONTEXT_FACTORY);
259 }
260 return _visitContextFactory;
261 }
262
263 private static class RestoreStateCallback implements VisitCallback
264 {
265 private PostRestoreStateEvent event;
266
267 public VisitResult visit(VisitContext context, UIComponent target)
268 {
269 if (event == null)
270 {
271 event = new PostRestoreStateEvent(target);
272 }
273 else
274 {
275 event.setComponent(target);
276 }
277
278
279
280
281 target.processEvent(event);
282
283 return VisitResult.ACCEPT;
284 }
285 }
286
287 @Deprecated
288 public String deriveViewId(FacesContext context, String viewId)
289 {
290
291 if (viewId == null)
292 {
293 return null;
294 }
295 FacesServletMapping mapping = getFacesServletMapping(context);
296 if (mapping == null || mapping.isExtensionMapping())
297 {
298 viewId = handleSuffixMapping(context, viewId);
299 }
300 else if(mapping.isPrefixMapping())
301 {
302 viewId = handlePrefixMapping(viewId,mapping.getPrefix());
303
304
305
306
307
308 if (viewId != null && viewId.equals(mapping.getPrefix()) &&
309 !ExternalContextUtils.isPortlet(context.getExternalContext()))
310 {
311 throw new InvalidViewIdException();
312 }
313 }
314 else if (viewId != null && mapping.getUrlPattern().startsWith(viewId))
315 {
316 throw new InvalidViewIdException(viewId);
317 }
318
319
320
321
322
323
324 return viewId;
325 }
326
327 protected String[] getContextSuffix(FacesContext context)
328 {
329 String defaultSuffix = context.getExternalContext().getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
330 if (defaultSuffix == null)
331 {
332 defaultSuffix = ViewHandler.DEFAULT_SUFFIX;
333 }
334 return defaultSuffix.split(" ");
335 }
336
337 protected String getFaceletsContextSuffix(FacesContext context)
338 {
339 String defaultSuffix = context.getExternalContext().getInitParameter(ViewHandler.FACELETS_SUFFIX_PARAM_NAME);
340 if (defaultSuffix == null)
341 {
342 defaultSuffix = ViewHandler.DEFAULT_FACELETS_SUFFIX;
343 }
344 return defaultSuffix;
345 }
346
347
348
349 protected String[] getFaceletsViewMappings(FacesContext context)
350 {
351 String faceletsViewMappings
352 = context.getExternalContext().getInitParameter(ViewHandler.FACELETS_VIEW_MAPPINGS_PARAM_NAME);
353 if(faceletsViewMappings == null)
354 {
355 faceletsViewMappings= context.getExternalContext().getInitParameter("facelets.VIEW_MAPPINGS");
356 }
357
358 return faceletsViewMappings == null ? null : faceletsViewMappings.split(";");
359 }
360
361
362
363
364
365
366
367
368 protected String handlePrefixMapping(String viewId, String prefix)
369 {
370
371
372
373
374
375
376
377 String uri = viewId;
378 if ( "".equals(prefix) )
379 {
380
381
382
383 prefix = "//";
384 }
385 else
386 {
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412 return uri;
413 }
414
415
416
417
418
419
420
421 protected String handleSuffixMapping(FacesContext context, String requestViewId)
422 {
423 String[] faceletsViewMappings = _initialized ? _faceletsViewMappings : getFaceletsViewMappings(context);
424 String[] jspDefaultSuffixes = _initialized ? _contextSuffixes : getContextSuffix(context);
425
426 int slashPos = requestViewId.lastIndexOf('/');
427 int extensionPos = requestViewId.lastIndexOf('.');
428
429
430 for (String defaultSuffix : jspDefaultSuffixes)
431 {
432 StringBuilder builder = new StringBuilder(requestViewId);
433
434 if (extensionPos > -1 && extensionPos > slashPos)
435 {
436 builder.replace(extensionPos, requestViewId.length(), defaultSuffix);
437 }
438 else
439 {
440 builder.append(defaultSuffix);
441 }
442 String candidateViewId = builder.toString();
443
444 if( faceletsViewMappings != null && faceletsViewMappings.length > 0 )
445 {
446 for (String mapping : faceletsViewMappings)
447 {
448 if(mapping.startsWith("/"))
449 {
450 continue;
451 }
452 if(mapping.equals(candidateViewId))
453 {
454 return candidateViewId;
455 }
456 if(mapping.startsWith("."))
457 {
458 builder.setLength(0);
459 builder.append(candidateViewId);
460 builder.replace(candidateViewId.lastIndexOf('.'), candidateViewId.length(), mapping);
461 String tempViewId = builder.toString();
462 if(checkResourceExists(context,tempViewId))
463 {
464 return tempViewId;
465 }
466 }
467 }
468 }
469
470
471 if(checkResourceExists(context,candidateViewId))
472 {
473 return candidateViewId;
474 }
475
476 }
477
478
479 String faceletsDefaultSuffix = _initialized ? _faceletsContextSufix : this.getFaceletsContextSuffix(context);
480 if (faceletsDefaultSuffix != null)
481 {
482 for (String defaultSuffix : jspDefaultSuffixes)
483 {
484 if (faceletsDefaultSuffix.equals(defaultSuffix))
485 {
486 faceletsDefaultSuffix = null;
487 break;
488 }
489 }
490 }
491 if (faceletsDefaultSuffix != null)
492 {
493 StringBuilder builder = new StringBuilder(requestViewId);
494
495 if (extensionPos > -1 && extensionPos > slashPos)
496 {
497 builder.replace(extensionPos, requestViewId.length(), faceletsDefaultSuffix);
498 }
499 else
500 {
501 builder.append(faceletsDefaultSuffix);
502 }
503
504 String candidateViewId = builder.toString();
505 if(checkResourceExists(context,candidateViewId))
506 {
507 return candidateViewId;
508 }
509 }
510
511
512 if(checkResourceExists(context,requestViewId))
513 {
514 return requestViewId;
515 }
516
517
518 return null;
519 }
520 protected boolean checkResourceExists(FacesContext context, String viewId)
521 {
522 try
523 {
524 if (isCheckedViewIdCachingEnabled(context))
525 {
526 Boolean resourceExists = getCheckedViewIDMap(context).get(
527 viewId);
528 if (resourceExists == null)
529 {
530 ViewDeclarationLanguage vdl = context.getApplication().getViewHandler()
531 .getViewDeclarationLanguage(context, viewId);
532 if (vdl != null)
533 {
534 resourceExists = vdl.viewExists(context, viewId);
535 }
536 else
537 {
538
539 resourceExists = context.getExternalContext().getResource(
540 viewId) != null;
541 }
542 getCheckedViewIDMap(context).put(viewId, resourceExists);
543 }
544 return resourceExists;
545 }
546 else
547 {
548 ViewDeclarationLanguage vdl = context.getApplication().getViewHandler()
549 .getViewDeclarationLanguage(context, viewId);
550 if (vdl != null)
551 {
552 if (vdl.viewExists(context, viewId))
553 {
554 return true;
555 }
556 }
557 else
558 {
559
560 if (context.getExternalContext().getResource(viewId) != null)
561 {
562 return true;
563 }
564 }
565 }
566 }
567 catch(MalformedURLException e)
568 {
569
570 }
571 return false;
572 }
573
574 public boolean checkViewExists(FacesContext facesContext, String viewId)
575 {
576 return checkResourceExists(facesContext, viewId);
577 }
578
579
580
581
582
583 protected FacesServletMapping getFacesServletMapping(FacesContext context)
584 {
585 Map<Object, Object> attributes = context.getAttributes();
586
587
588 FacesServletMapping mapping = (FacesServletMapping) attributes.get(CACHED_SERVLET_MAPPING);
589 if (mapping == null)
590 {
591 ExternalContext externalContext = context.getExternalContext();
592 mapping = calculateFacesServletMapping(externalContext.getRequestServletPath(),
593 externalContext.getRequestPathInfo());
594
595 attributes.put(CACHED_SERVLET_MAPPING, mapping);
596 }
597 return mapping;
598 }
599
600
601
602
603
604
605
606
607
608
609 protected static FacesServletMapping calculateFacesServletMapping(
610 String servletPath, String pathInfo)
611 {
612 if (pathInfo != null)
613 {
614
615
616
617
618
619
620 return FacesServletMapping.createPrefixMapping(servletPath);
621 }
622 else
623 {
624
625
626
627
628
629 int slashPos = servletPath.lastIndexOf('/');
630 int extensionPos = servletPath.lastIndexOf('.');
631 if (extensionPos > -1 && extensionPos > slashPos)
632 {
633 String extension = servletPath.substring(extensionPos);
634 return FacesServletMapping.createExtensionMapping(extension);
635 }
636 else
637 {
638
639
640 return FacesServletMapping.createPrefixMapping(servletPath);
641 }
642 }
643 }
644
645 private ConcurrentLRUCache<String, Boolean> getCheckedViewIDMap(FacesContext context)
646 {
647 if (_checkedViewIdMap == null)
648 {
649 int maxSize = getViewIDCacheMaxSize(context);
650 _checkedViewIdMap = new ConcurrentLRUCache<String, Boolean>((maxSize * 4 + 3) / 3, maxSize);
651 }
652 return _checkedViewIdMap;
653 }
654
655 private boolean isCheckedViewIdCachingEnabled(FacesContext context)
656 {
657 if (_checkedViewIdCacheEnabled == null)
658 {
659
660 if (context.isProjectStage(ProjectStage.Development))
661 {
662 _checkedViewIdCacheEnabled = Boolean.FALSE;
663 }
664 else
665 {
666
667 _checkedViewIdCacheEnabled = WebConfigParamUtils.getBooleanInitParameter(context.getExternalContext(),
668 CHECKED_VIEWID_CACHE_ENABLED_ATTRIBUTE,
669 CHECKED_VIEWID_CACHE_ENABLED_DEFAULT);
670 }
671
672 if (log.isLoggable(Level.FINE))
673 {
674 log.log(Level.FINE, "MyFaces ViewID Caching Enabled="
675 + _checkedViewIdCacheEnabled);
676 }
677 }
678 return _checkedViewIdCacheEnabled;
679 }
680
681 private int getViewIDCacheMaxSize(FacesContext context)
682 {
683 ExternalContext externalContext = context.getExternalContext();
684
685 return WebConfigParamUtils.getIntegerInitParameter(externalContext,
686 CHECKED_VIEWID_CACHE_SIZE_ATTRIBUTE, CHECKED_VIEWID_CACHE_DEFAULT_SIZE);
687 }
688 }