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