1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.myfaces.trinidadinternal.application;
20
21 import java.io.IOException;
22 import java.io.Serializable;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Map;
29
30 import javax.faces.FactoryFinder;
31 import javax.faces.application.StateManager;
32 import javax.faces.application.StateManagerWrapper;
33 import javax.faces.component.UIComponent;
34 import javax.faces.component.UIViewRoot;
35 import javax.faces.context.ExternalContext;
36 import javax.faces.context.FacesContext;
37 import javax.faces.render.RenderKit;
38 import javax.faces.render.RenderKitFactory;
39 import javax.faces.render.ResponseStateManager;
40
41 import org.apache.myfaces.trinidad.component.UIXComponentBase;
42 import org.apache.myfaces.trinidad.context.RequestContext;
43 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
44 import org.apache.myfaces.trinidad.util.ExternalContextUtils;
45 import org.apache.myfaces.trinidadinternal.util.LRUCache;
46 import org.apache.myfaces.trinidadinternal.util.SubKeyMap;
47 import org.apache.myfaces.trinidadinternal.util.TokenCache;
48
49 import com.sun.facelets.FaceletViewHandler;
50
51 /**
52 * StateManager that handles a hybrid client/server strategy: a
53 * SerializedView is stored on the server, and only a small token
54 * is stored on the client.
55 * <p>
56 * <h3>Application View cache</h3>
57 * <p>
58 * In addition, an optional Application view cache is supported.
59 * This view cache will, when enabled, perform special caching
60 * of all state for non-postback requests (that is, the initial
61 * state of all pages). For all pages, their SerializedView state
62 * is stored in a Map at application scope, and reused across
63 * all users. This simultaneously eliminates the expense of saving
64 * the state at all (except for the first request for any page),
65 * and significantly reduces memory usage as long as users are
66 * largely viewing initial pages only.
67 * <p>
68 * In addition, because the viewId is sufficient to identify the
69 * page state out of the cache, the token can be completely
70 * constant across requests and users. This makes it possible
71 * to cache the page content (which is not possible otherwise).
72 * <p>
73 * Since application scope objects do not support failover,
74 * a mirror of the cache is saved at session scope. The mirror
75 * is an LRU map of the last 16 application-scoped entries, but
76 * since it stores precisely the same SerializedView instances
77 * as the application scope, the additional memory requirements
78 * are minimal.
79 * <p>
80 * @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/application/StateManagerImpl.java#2 $) $Date: 18-nov-2005.16:12:04 $
81 */
82 public class StateManagerImpl extends StateManagerWrapper
83 {
84 static public final String USE_APPLICATION_VIEW_CACHE_INIT_PARAM =
85 "org.apache.myfaces.trinidad.USE_APPLICATION_VIEW_CACHE";
86
87 static public final String CACHE_VIEW_ROOT_INIT_PARAM =
88 "org.apache.myfaces.trinidad.CACHE_VIEW_ROOT";
89
90
91 /**
92 * Servlet context initialization parameter used by
93 * StateManagerImpl to decide what sort of state should be saved
94 * on the client. Valid values are "token" and "all"; the
95 * default is "token".
96 */
97 static public final String CLIENT_STATE_METHOD_PARAM_NAME =
98 "org.apache.myfaces.trinidad.CLIENT_STATE_METHOD";
99
100 /**
101 * Servlet context initialization parameter used by
102 * StateManagerImpl to decide how many tokens can be stored
103 * per user. The default is 15.
104 */
105 static public final String CLIENT_STATE_MAX_TOKENS_PARAM_NAME =
106 "org.apache.myfaces.trinidad.CLIENT_STATE_MAX_TOKENS";
107
108 /**
109 * Value indicating that only a simple token will be stored
110 * on the client.
111 */
112 static public final String CLIENT_STATE_METHOD_TOKEN = "token";
113
114 /**
115 * Value indicating that the entire component state will be stored
116 * on the client.
117 */
118 static public final String CLIENT_STATE_METHOD_ALL = "all";
119
120 public StateManagerImpl(
121 StateManager delegate)
122 {
123 _delegate = delegate;
124 }
125
126 @Override
127 protected StateManager getWrapped()
128 {
129 return _delegate;
130 }
131
132 @SuppressWarnings("deprecation")
133 @Override
134 public Object saveView(FacesContext context)
135 {
136 assert(context != null);
137
138 if(isSavingStateInClient(context))
139 {
140 SerializedView view = _saveSerializedView(context);
141 return new Object[]{view.getStructure(), view.getState()};
142 }
143
144 return super.saveView(context);
145 }
146
147 @Override @SuppressWarnings("deprecation")
148 public SerializedView saveSerializedView(FacesContext context)
149 {
150 assert(context != null);
151
152 if(isSavingStateInClient(context))
153 {
154 return _saveSerializedView(context);
155 }
156
157 return _delegate.saveSerializedView(context);
158 }
159
160 /**
161 * Save a component tree as an Object.
162 */
163 static public Object saveComponentTree(
164 FacesContext context,
165 UIComponent component)
166 {
167 // Don't remove transient components...
168 Object structure = new Structure(component);
169 Object state = component.processSaveState(context);
170 return new PageState(context, structure, state, null);
171 }
172
173 /**
174 * Take an object created by saveComponentTree()
175 * and instantiate it as a UIComponent.
176 */
177 static public UIComponent restoreComponentTree(
178 FacesContext context,
179 Object savedState) throws ClassNotFoundException,
180 InstantiationException,
181 IllegalAccessException
182 {
183 if (savedState == null)
184 throw new NullPointerException();
185
186 if (!(savedState instanceof PageState))
187 throw new IllegalArgumentException(_LOG.getMessage(
188 "INVALID_SAVED_STATE_OBJECT"));
189
190 PageState viewState = (PageState) savedState;
191
192 Object structure = viewState.getStructure();
193 Object state = viewState.getState();
194
195 UIComponent component =
196 ((Structure) structure).createComponent();
197
198 if (state != null)
199 component.processRestoreState(context, state);
200
201 return component;
202 }
203
204
205 /**
206 * Save a view root. Doesn't return a SerializedView because
207 * SerializedView is a non-static inner class, and this needs
208 * to be a static method.
209 */
210 static public Object saveViewRoot(
211 FacesContext context,
212 UIViewRoot root)
213 {
214 _removeTransientComponents(root);
215
216 Object structure = new Structure(root);
217 Object state = root.processSaveState(context);
218 return new PageState(context, structure, state, root);
219 }
220
221 static public UIViewRoot restoreViewRoot(
222 FacesContext context,
223 Object saved) throws ClassNotFoundException, InstantiationException,
224 IllegalAccessException
225
226 {
227 if (saved == null)
228 throw new NullPointerException();
229
230 PageState viewState = (PageState) saved;
231
232 UIViewRoot root = viewState.popRoot(context);
233 if (root != null)
234 {
235 return root; // bug 4712492
236 }
237
238 Object structure = viewState.getStructure();
239 Object state = viewState.getState();
240
241 root = (UIViewRoot)
242 ((Structure) structure).createComponent();
243
244 if (state != null)
245 root.processRestoreState(context, state);
246
247 return root;
248 }
249
250 @SuppressWarnings({"unchecked", "deprecation"})
251 private SerializedView _saveSerializedView(FacesContext context)
252 {
253 SerializedView view = _getCachedSerializedView(context);
254 if (view != null)
255 return view;
256
257 UIViewRoot root = context.getViewRoot();
258 boolean dontSave = false;
259
260 // See if we're going to use the application view cache for
261 // this request
262 Map<String, Object> applicationViewCache = null;
263 Map<String, Object> perSessionApplicationViewCache = null;
264 if (_useApplicationViewCache(context))
265 {
266 // OK, we are: so find the application cache and
267 // the per-session mirror
268 applicationViewCache = _getApplicationViewCache(context);
269 perSessionApplicationViewCache =
270 _getPerSessionApplicationViewCache(context);
271
272 synchronized (applicationViewCache)
273 {
274 // If we've already got a copy of the state stored, then
275 // we just need to make sure it's mirrored on the session
276 Object applicationState = applicationViewCache.get(root.getViewId());
277 if (applicationState != null)
278 {
279 // Note that we've got no work to do...
280 dontSave = true;
281 perSessionApplicationViewCache.put(root.getViewId(),
282 applicationState);
283 }
284 }
285 }
286
287 _removeTransientComponents(root);
288
289 Object structure = (dontSave || !_needStructure(context))
290 ? null
291 : new Structure(root);
292 Object state = dontSave ? null : root.processSaveState(context);
293
294 if (_saveAsToken(context))
295 {
296 String token;
297 if (applicationViewCache == null)
298 {
299 assert(!dontSave);
300 TokenCache cache = _getViewCache(context);
301 assert(cache != null);
302
303 // Store bits of the session as subkeys off of the session
304 Map<String, Object> stateMap = new SubKeyMap(
305 context.getExternalContext().getSessionMap(),
306 _VIEW_CACHE_KEY + ".");
307 // Sadly, we can't save just a SerializedView, because we should
308 // save a serialized object, and SerializedView is a *non*-static
309 // inner class of StateManager
310 PageState pageState = new PageState(
311 context,
312 structure,
313 state,
314 // Save the view root into the page state as a transient
315 // if this feature has not been disabled
316 _useViewRootCache(context) ? root : null);
317
318 String requestToken = _getRequestTokenForResponse(context);
319 // If we have a cached token that we want to reuse,
320 // and that token hasn't disappeared from the cache already
321 // (unlikely, but not impossible), use the stateMap directly
322 // without asking the cache for a new token
323 if ((requestToken != null) && cache.isAvailable(requestToken))
324 {
325 // NOTE: under *really* high pressure, the cache might
326 // have been emptied between the isAvailable() call and
327 // this put(). This seems sufficiently implausible to
328 // be worth punting on
329 stateMap.put(requestToken, pageState);
330 token = requestToken;
331 // NOTE 2: we have not pinned this reused state to any old state
332 // This is OK for current uses of pinning and state reuse,
333 // as pinning stays constant within a window, and we're not
334 // erasing pinning at all.
335 }
336 else
337 {
338 // See if we should pin this new state to any old state
339 String pinnedToken = (String)
340 context.getExternalContext().getRequestMap().get(_PINNED_STATE_TOKEN_KEY);
341 token = cache.addNewEntry(pageState,
342 stateMap,
343 pinnedToken);
344 }
345 }
346 // If we got the "applicationViewCache", we're using it.
347 else
348 {
349 // use null viewRoot since this state is shared across users:
350 Object applicationState = new PageState(context, structure, state, null);
351 // If we need to, stash the state off in our cache
352 if (!dontSave)
353 {
354 synchronized (applicationViewCache)
355 {
356 applicationViewCache.put(root.getViewId(),
357 applicationState);
358 perSessionApplicationViewCache.put(root.getViewId(),
359 applicationState);
360 }
361 }
362
363 token = _APPLICATION_CACHE_TOKEN;
364 }
365
366 assert(token != null);
367
368 // Create a "tokenView" which abuses SerializedView to store
369 // our token only
370 view = new SerializedView(token, null);
371
372 // And store the token for this request
373 context.getExternalContext().getRequestMap().put(_REQUEST_STATE_TOKEN_KEY,
374 token);
375 }
376 else
377 {
378 assert(!dontSave);
379 view = new SerializedView(structure, state);
380 }
381
382 _saveCachedSerializedView(context, view);
383
384 return view;
385 }
386
387 /**
388 * Requests that an old state token be "pinned" to the state of
389 * the current request. This means that the view state corresponding
390 * to the token will not be released before the state for this request
391 * is released.
392 */
393 @SuppressWarnings("unchecked")
394 static public void pinStateToRequest(FacesContext context, String stateToken)
395 {
396 context.getExternalContext().getRequestMap().put(
397 _PINNED_STATE_TOKEN_KEY, stateToken);
398
399 }
400
401 /**
402 * @return the state token for the current request
403 */
404 static public String getStateToken(FacesContext context)
405 {
406 return (String) context.getExternalContext().getRequestMap().get(
407 _REQUEST_STATE_TOKEN_KEY);
408 }
409
410
411 /**
412 * Mark the the incoming request token should be used for the response
413 */
414 @SuppressWarnings("unchecked")
415 static public void reuseRequestTokenForResponse(ExternalContext ec)
416 {
417 ec.getRequestMap().put(_REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY, Boolean.TRUE);
418 }
419
420 /**
421 * Clears the flag indicating that the old request token should be used for the response.
422 */
423 @SuppressWarnings("unchecked")
424 static public void clearReuseRequestTokenForResponse(ExternalContext ec)
425 {
426 ec.getRequestMap().remove(_REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY);
427 }
428
429 /**
430 * If we've been asked to reuse the request token for the response,
431 * store it off.
432 */
433 @SuppressWarnings("unchecked")
434 static private void _updateRequestTokenForResponse(
435 FacesContext context, String token)
436 {
437 Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
438 // Go from TRUE -> the saved token
439 if (Boolean.TRUE.equals(
440 requestMap.get(_REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY)))
441 {
442 requestMap.put(_REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY, token);
443 }
444 }
445
446
447 /**
448 * Get any cached token for the response.
449 */
450 @SuppressWarnings("unchecked")
451 static private String _getRequestTokenForResponse(
452 FacesContext context)
453 {
454 Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
455 Object token = requestMap.get(_REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY);
456 // We wanted to, but didn't have anything saved
457 if (Boolean.TRUE.equals(token))
458 return null;
459
460 return (String) token;
461 }
462
463
464 @Override @SuppressWarnings("deprecation")
465 public void writeState(FacesContext context,
466 SerializedView state) throws IOException
467 {
468 _delegate.writeState(context, state);
469 }
470
471 @SuppressWarnings({"unchecked", "deprecation"})
472 @Override
473 public UIViewRoot restoreView(FacesContext context, String viewId,
474 String renderKitId)
475 {
476 if (!isSavingStateInClient(context))
477 return _delegate.restoreView(context, viewId, renderKitId);
478
479 final Object structure;
480 final Object state;
481 boolean recalculateLocale = false;
482
483 ResponseStateManager rsm = _getResponseStateManager(context, renderKitId);
484 if (_saveAsToken(context))
485 {
486 Object token = rsm.getTreeStructureToRestore(context, viewId);
487 if (token == null)
488 {
489 _LOG.finest("No token in the request for view \"{0}\"; " +
490 "probably a first view.", viewId);
491 return null;
492 }
493
494 assert(token instanceof String);
495 _LOG.finer("Restoring saved view state for token {0}", token);
496
497 PageState viewState;
498
499 // Load from the application cache
500 if (_APPLICATION_CACHE_TOKEN.equals(token))
501 {
502 Map<String, Object> cache = _getApplicationViewCache(context);
503 Map<String, Object> perSessionCache =
504 _getPerSessionApplicationViewCache(context);
505
506 // Synchronize on the application-level cache.
507 // =-=AEW This may produce excessive contention
508 synchronized (cache)
509 {
510 // Look first in the per-session cache
511 viewState = (PageState) perSessionCache.get(viewId);
512 if (viewState == null)
513 {
514 // Nope, it's not there. Look in the application cache
515 viewState = (PageState) cache.get(viewId);
516 // And if we find it there, then push it back into
517 // the per-session cache (it may have expired)
518 if (viewState != null)
519 perSessionCache.put(viewId, viewState);
520 }
521
522 // If the view was found in the application cache then we
523 // know it would be unsafe to use its locale for this session.
524 // Same conclusion, however, even if found in the per-session
525 // cache, since the latter is just a mirror of the former.
526 recalculateLocale = true;
527 }
528 }
529 else
530 {
531 Map<String, Object> stateMap = new SubKeyMap(
532 context.getExternalContext().getSessionMap(),
533 _VIEW_CACHE_KEY + ".");
534 viewState = (PageState) stateMap.get(token);
535
536 if (viewState != null)
537 _updateRequestTokenForResponse(context, (String) token);
538
539 // Make sure that if the view state is present, the cache still
540 // has the token, and vice versa
541
542 // NOTE: it's very important that we call through to the
543 // token cache here, not just inside the assert. If we don't,
544 // then we don't actually access the token, so it doesn't
545 // get bumped up to the front in the LRU Cache!
546 boolean isAvailable =
547 _getViewCache(context).isAvailable((String) token);
548 assert ((viewState != null) == isAvailable);
549 }
550
551 if (viewState == null)
552 {
553 _LOG.severe("CANNOT_FIND_SAVED_VIEW_STATE", token);
554 return null;
555 }
556
557 _LOG.fine("Successfully found view state for token {0}", token);
558
559 UIViewRoot root = viewState.popRoot(context); // bug 4712492
560 if (root != null)
561 {
562 _LOG.finer("UIViewRoot for token {0} already exists. Bypassing restoreState", token);
563 return root;
564 }
565
566 structure = viewState.getStructure();
567 state = viewState.getState();
568 }
569 else
570 {
571 structure = rsm.getTreeStructureToRestore(context, viewId);
572 state = rsm.getComponentStateToRestore(context);
573 }
574
575 if (structure == null)
576 {
577
578 UIViewRoot root = context.getViewRoot();
579 if (root == null && _needStructure(context))
580 {
581 _LOG.severe("NO_STRUCTURE_ROOT_AVAILABLE");
582 return null;
583 }
584
585 if (state != null)
586 root.processRestoreState(context, state);
587
588 return root;
589 }
590 else
591 {
592 if (!(structure instanceof Structure))
593 {
594 _LOG.severe("NO_STRUCTURE_AVAILABLE");
595 return null;
596 }
597
598 // OK, we've structure and state; let's see what we can do!
599 try
600 {
601 UIViewRoot root = (UIViewRoot)
602 ((Structure) structure).createComponent();
603
604 if (state != null)
605 root.processRestoreState(context, state);
606
607 if (recalculateLocale)
608 {
609 // Ensure that locale gets re-calculated when next fetched.
610 root.setLocale((Locale) null);
611 }
612
613 _LOG.finer("Restored state for view \"{0}\"", viewId);
614 return root;
615 }
616 catch (ClassNotFoundException cnfe)
617 {
618 _LOG.severe(cnfe);
619 }
620 catch (InstantiationException ie)
621 {
622 _LOG.severe(ie);
623 }
624 catch (IllegalAccessException iae)
625 {
626 _LOG.severe(iae);
627 }
628 }
629
630 return null;
631 }
632
633 @Override
634 public boolean isSavingStateInClient(FacesContext context)
635 {
636 return _delegate.isSavingStateInClient(context);
637 }
638
639 //
640 // Protected APIs: we don't want
641 //
642
643 @Override
644 protected Object getTreeStructureToSave(FacesContext context)
645 {
646 throw new UnsupportedOperationException();
647 }
648
649 @Override
650 protected Object getComponentStateToSave(FacesContext context)
651 {
652 throw new UnsupportedOperationException();
653 }
654
655 @Override
656 protected UIViewRoot restoreTreeStructure
657 (FacesContext context, String viewId, String renderKitId)
658 {
659 throw new UnsupportedOperationException();
660 }
661
662 @Override
663 protected void restoreComponentState
664 (FacesContext context, UIViewRoot viewRoot, String renderKitId)
665 {
666 throw new UnsupportedOperationException();
667 }
668
669
670 private TokenCache _getViewCache(FacesContext context)
671 {
672 return TokenCache.getTokenCacheFromSession(context,
673 _VIEW_CACHE_KEY,
674 true,
675 _getCacheSize(context));
676 }
677
678 /**
679 * Tests whether to send a small string token, or the entire
680 * serialized component state to the client-side.
681 * @return true, if the small string token is to be sent to the client-side.
682 */
683 private boolean _saveAsToken(FacesContext context)
684 {
685 ExternalContext external = context.getExternalContext();
686 Object clientMethod =
687 external.getInitParameterMap().get(CLIENT_STATE_METHOD_PARAM_NAME);
688 if ((clientMethod != null) &&
689 CLIENT_STATE_METHOD_ALL.equalsIgnoreCase((String) clientMethod))
690 return false;
691
692 return true;
693 }
694
695 private int _getCacheSize(FacesContext context)
696 {
697 ExternalContext external = context.getExternalContext();
698 Object maxTokens =
699 external.getInitParameterMap().get(CLIENT_STATE_MAX_TOKENS_PARAM_NAME);
700 if (maxTokens != null)
701 {
702 try
703 {
704 return Math.max(1, Integer.parseInt((String) maxTokens));
705 }
706 catch (NumberFormatException nfe)
707 {
708 _LOG.warning("Ignoring servlet init parameter:"+CLIENT_STATE_MAX_TOKENS_PARAM_NAME+
709 "\n unable to parse:"+maxTokens, nfe);
710 _LOG.warning(nfe);
711 }
712 }
713
714 return _DEFAULT_CACHE_SIZE;
715 }
716
717 //
718 // @todo Map is a bad structure
719 // @todo a static size is bad
720 //
721 @SuppressWarnings("unchecked")
722 static private Map<String, Object> _getApplicationViewCache(FacesContext context)
723 {
724 synchronized (_APPLICATION_VIEW_CACHE_LOCK)
725 {
726 Map<String, Object> appMap = context.getExternalContext().getApplicationMap();
727 Map<String, Object> cache = (Map<String, Object>) appMap.get(_APPLICATION_VIEW_CACHE_KEY);
728 if (cache == null)
729 {
730 cache = new HashMap<String, Object>(128);
731 appMap.put(_APPLICATION_VIEW_CACHE_KEY, cache);
732 }
733
734 return cache;
735 }
736 }
737
738 @SuppressWarnings("unchecked")
739 static private Map<String, Object> _getPerSessionApplicationViewCache(FacesContext context)
740 {
741 ExternalContext external = context.getExternalContext();
742 Object session = external.getSession(true);
743 assert(session != null);
744
745 Map<String, Object> cache;
746 // Synchronize on the session object to ensure that
747 // we don't ever create two different caches
748 synchronized (session)
749 {
750 Map<String, Object> sessionMap = external.getSessionMap();
751 cache = (Map<String, Object>) sessionMap.get(_APPLICATION_VIEW_CACHE_KEY);
752 if (cache == null)
753 {
754 cache = _createPerSessionApplicationViewCache();
755 sessionMap.put(_APPLICATION_VIEW_CACHE_KEY, cache);
756 }
757 }
758
759 return cache;
760 }
761
762 //
763 // For the per-session mirror of the application view cache,
764 // use an LRU LinkedHashMap to store the latest 16 pages.
765 //
766 static private Map<String, Object> _createPerSessionApplicationViewCache()
767 {
768 return new LRUCache<String, Object>(_MAX_PER_SESSION_APPLICATION_SIZE);
769 }
770
771 static private final int _MAX_PER_SESSION_APPLICATION_SIZE = 16;
772
773 //
774 // Use the application view cache if and only if:
775 // (1) We're saving state tokens on the client
776 // (2) This is *not* a postback request
777 // (3) The feature has been explicitly enabled
778 //
779 private boolean _useApplicationViewCache(FacesContext context)
780 {
781 if (_useApplicationViewCache == Boolean.FALSE)
782 return false;
783
784 if (_saveAsToken(context) &&
785 // Note: do not use TrinidadPhaseListener, as that
786 // will return "true" even after navigation has occured,
787 // but the Application View Cache is still fine.
788 //!TrinidadPhaseListener.isPostback(context)
789 !RequestContext.getCurrentInstance().isPostback())
790 {
791 if (_useApplicationViewCache == null)
792 {
793 String s = context.getExternalContext().getInitParameter(
794 USE_APPLICATION_VIEW_CACHE_INIT_PARAM);
795 _useApplicationViewCache =
796 "true".equalsIgnoreCase(s) ? Boolean.TRUE : Boolean.FALSE;
797 }
798
799 return _useApplicationViewCache.booleanValue();
800 }
801
802 return false;
803 }
804
805 private boolean _useViewRootCache(FacesContext context)
806 {
807 if (_useViewRootCache == null)
808 {
809 String s = context.getExternalContext().getInitParameter(
810 CACHE_VIEW_ROOT_INIT_PARAM);
811 _useViewRootCache =
812 (!"false".equalsIgnoreCase(s)) ? Boolean.TRUE : Boolean.FALSE;
813 }
814
815 return _useViewRootCache.booleanValue();
816 }
817
818
819
820 private boolean _needStructure(FacesContext context)
821 {
822 if (_structureGeneratedByTemplate == null)
823 {
824 ExternalContext external = context.getExternalContext();
825 String restoreMode = external.getInitParameter(
826 FaceletViewHandler.PARAM_BUILD_BEFORE_RESTORE);
827 if ("true".equals(restoreMode))
828 _structureGeneratedByTemplate = Boolean.TRUE;
829 else
830 _structureGeneratedByTemplate = Boolean.FALSE;
831 }
832
833 return !_structureGeneratedByTemplate.booleanValue();
834 }
835
836 static private ResponseStateManager _getResponseStateManager(
837 FacesContext context,
838 String renderKitId)
839 {
840 RenderKitFactory factory = (RenderKitFactory)
841 FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
842 RenderKit kit = factory.getRenderKit(context, renderKitId);
843 return kit.getResponseStateManager();
844 }
845
846 @SuppressWarnings("unchecked")
847 static private void _removeTransientComponents(
848 UIComponent root)
849 {
850 List<UIComponent> components = new ArrayList<UIComponent>();
851 _gatherTransientComponents(root, components);
852 Iterator<UIComponent> iter = components.iterator();
853 while (iter.hasNext())
854 {
855 UIComponent kid = iter.next();
856 UIComponent parent = kid.getParent();
857 // First, see if its a child
858 if (parent.getChildCount() > 0)
859 {
860 List<UIComponent> children = parent.getChildren();
861 if (children.remove(kid))
862 {
863 continue;
864 }
865 }
866
867 // Nope, guess it's a facet
868 // 2006-08-02: -= Simon Lessard
869 // Not 1.5 structure and inefficient loop
870 // values() is more efficient as you don't have
871 // to do a second lookup for the value.
872 Map<String, UIComponent> facets = parent.getFacets();
873 for(Iterator<UIComponent> facetIter = facets.values().iterator();
874 facetIter.hasNext();)
875 {
876 if(facetIter.next() == kid)
877 {
878 facetIter.remove();
879 // FIXME: -= Simon Lessard
880 // Is that continue need to labeled to go all the way up to
881 // the first while? Currently it won't cause any problem, but
882 // it's a performance loss.
883 continue;
884 }
885 }
886
887 // Couldn't find the component at all in its parent: that's
888 // not good.
889 assert false;
890 }
891 }
892
893 @SuppressWarnings("unchecked")
894 static private void _gatherTransientComponents(
895 UIComponent component, List<UIComponent> componentsToRemove)
896 {
897 Iterator<UIComponent> kids = component.getFacetsAndChildren();
898 while (kids.hasNext())
899 {
900 UIComponent kid = kids.next();
901 // UIXComponentBase doesn't mind transient components
902 // in its saved state, so don't bother with this.
903 if (!(component instanceof UIXComponentBase) &&
904 kid.isTransient())
905 {
906 componentsToRemove.add(kid);
907 }
908 else
909 {
910 _gatherTransientComponents(kid, componentsToRemove);
911 }
912 }
913 }
914
915 @SuppressWarnings("deprecation")
916 private SerializedView _getCachedSerializedView(
917 FacesContext context)
918 {
919 return (SerializedView) context.getExternalContext().
920 getRequestMap().get(_CACHED_SERIALIZED_VIEW);
921 }
922
923 @SuppressWarnings({"unchecked","deprecation"})
924 private void _saveCachedSerializedView(
925 FacesContext context, SerializedView state)
926 {
927 context.getExternalContext().getRequestMap().put(_CACHED_SERIALIZED_VIEW,
928 state);
929 }
930
931 private static final class PageState implements Serializable
932 {
933 private static final long serialVersionUID = 1L;
934
935 private final Object _structure, _state;
936 // use transient since UIViewRoots are not Serializable.
937 private transient UIViewRoot _root;
938 // If the UIViewRoot is lost, then this state is useless, so mark it
939 // transient as well:
940 private transient Object _viewRootState;
941
942 public PageState(FacesContext fc, Object structure, Object state, UIViewRoot root)
943 {
944 _structure = structure;
945 _state = state;
946 _root = root;
947 // we need this state, as we are going to recreate the UIViewRoot later. see
948 // the popRoot() method:
949 _viewRootState = (root != null) ? root.saveState(fc) : null;
950 }
951
952 public Object getStructure()
953 {
954 return _structure;
955 }
956
957 public Object getState()
958 {
959 return _state;
960 }
961
962 @SuppressWarnings("unchecked")
963 public UIViewRoot popRoot(FacesContext fc)
964 {
965 UIViewRoot root = null;
966 Object viewRootState = null;
967 // we need to synchronize because we are mutating _root
968 // which is shared between simultaneous requests from the same user:
969 synchronized(this)
970 {
971 if (_root != null)
972 {
973 root = _root;
974 viewRootState = _viewRootState;
975 // we must clear the cached viewRoot. This is because UIComponent trees
976 // are mutable and if the back button
977 // is used to come back to an old PageState, then it would be
978 // really bad if we reused that component tree:
979 _root = null;
980 _viewRootState = null;
981 }
982 }
983
984 if (root != null)
985 {
986 // If an error happens during updateModel, JSF 1.1 does not
987 // clear FacesEvents (or FacesMessages, IIRC), so any pending
988 // events will still be present, on the subsequent request.
989 // so to clear the events, we create a new UIViewRoot.
990 // must get the UIViewRoot from the application so that
991 // we pick up any custom ViewRoot defined in faces-config.xml:
992 UIViewRoot newRoot = (UIViewRoot)
993 fc.getApplication().createComponent(UIViewRoot.COMPONENT_TYPE);
994
995 //This code handles automatic namespacing in a JSR-301 environment
996 if(ExternalContextUtils.isPortlet(fc.getExternalContext()))
997 {
998 //To avoid introducing a runtime dependency on the bridge,
999 //this method should only be executed when we have a portlet
1000 //request. If we do have a portlet request then the bridge
1001 //should be available anyway.
1002 newRoot = PortletUtils.getPortletViewRoot(newRoot);
1003 }
1004
1005
1006 // must call restoreState so that we setup attributes, listeners,
1007 // uniqueIds, etc ...
1008 newRoot.restoreState(fc, viewRootState);
1009
1010 // we need to use a temp list because as a side effect of
1011 // adding a child to a UIComponent, that child is removed from
1012 // the parent UIComponent. So the following will break:
1013 // newRoot.getChildren().addAll(root.getChildren());
1014 // because "root"'s child List is being mutated as the List
1015 // is traversed.
1016 List<UIComponent> temp = new ArrayList<UIComponent>(root.getChildCount());
1017 temp.addAll(root.getChildren());
1018 newRoot.getChildren().addAll(temp);
1019 return newRoot;
1020 }
1021
1022 return null;
1023 }
1024 }
1025
1026 /* =-=AEW A utility function to dump out all the state that's getting
1027 sent over. To compile, you need to make most of TreeState public */
1028 /*
1029 static private void _dumpState(Object state, int depth)
1030 {
1031 String out = _SPACES.substring(0, depth * 2);
1032 Object objectState = state;
1033
1034 if (state instanceof org.apache.myfaces.trinidad.component.TreeState)
1035 objectState = ((org.apache.myfaces.trinidad.component.TreeState) state)._state;
1036
1037 if (objectState == null)
1038 out += "null";
1039 else if (objectState instanceof Object[])
1040 out += "array";
1041 else
1042 out += objectState.toString() + "(" + objectState.getClass() + ")";
1043
1044 if (objectState instanceof Object[])
1045 {
1046 Object[] array = (Object[]) objectState;
1047 for (int i = 0; i < array.length; i++)
1048 _dumpState(array[i], depth + 1);
1049 }
1050
1051
1052 if (state instanceof org.apache.myfaces.trinidad.component.TreeState)
1053 {
1054 Object[] array = ((org.apache.myfaces.trinidad.component.TreeState)state)._children;
1055 if (array != null)
1056 {
1057 for (int i = 0; i < array.length; i++)
1058 _dumpState(array[i], depth + 1);
1059 }
1060
1061 array = ((org.apache.myfaces.trinidad.component.TreeState)state)._facets;
1062 if (array != null)
1063 {
1064 for (int i = 0; i < array.length; i++)
1065 _dumpState(array[i], depth + 1);
1066 }
1067 }
1068 }
1069
1070 static private final String _SPACES = " ";
1071 */
1072
1073 private final StateManager _delegate;
1074 private Boolean _useViewRootCache;
1075 private Boolean _useApplicationViewCache;
1076 private Boolean _structureGeneratedByTemplate;
1077
1078
1079 private static final int _DEFAULT_CACHE_SIZE = 15;
1080
1081 private static final Object _APPLICATION_VIEW_CACHE_LOCK = new Object();
1082 private static final String _VIEW_CACHE_KEY =
1083 "org.apache.myfaces.trinidadinternal.application.VIEW_CACHE";
1084
1085 private static final String _APPLICATION_VIEW_CACHE_KEY =
1086 "org.apache.myfaces.trinidadinternal.application.APPLICATION_VIEW_CACHE";
1087
1088 private static final String _CACHED_SERIALIZED_VIEW =
1089 "org.apache.myfaces.trinidadinternal.application.CachedSerializedView";
1090
1091 private static final String _REQUEST_STATE_TOKEN_KEY =
1092 "org.apache.myfaces.trinidadinternal.application.REQUEST_STATE_TOKEN";
1093
1094 private static final String _PINNED_STATE_TOKEN_KEY =
1095 "org.apache.myfaces.trinidadinternal.application.PINNED_STATE_TOKEN";
1096
1097 private static final String _REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY =
1098 "org.apache.myfaces.trinidadinternal.application.REUSE_REQUEST_TOKEN_FOR_RESPONSE";
1099
1100
1101 private static final String _APPLICATION_CACHE_TOKEN = "_a_";
1102
1103 private static final long serialVersionUID = 1L;
1104
1105 private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(StateManagerImpl.class);
1106 }