All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.trinidadinternal.application.StateManagerImpl Maven / Gradle / Ivy

There is a newer version: 2.2.1
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.myfaces.trinidadinternal.application;

import com.sun.facelets.FaceletViewHandler;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

import javax.faces.FactoryFinder;
import javax.faces.application.StateManager;
import javax.faces.application.StateManagerWrapper;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.ResponseStateManager;

import org.apache.myfaces.trinidad.bean.util.StateUtils;
import org.apache.myfaces.trinidad.component.UIXComponentBase;
import org.apache.myfaces.trinidad.component.core.CoreDocument;
import org.apache.myfaces.trinidad.context.RequestContext;
import org.apache.myfaces.trinidad.context.Window;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.util.ExternalContextUtils;
import org.apache.myfaces.trinidad.util.TransientHolder;
import org.apache.myfaces.trinidadinternal.context.RequestContextImpl;
import org.apache.myfaces.trinidadinternal.context.TrinidadPhaseListener;
import org.apache.myfaces.trinidadinternal.util.LRUCache;
import org.apache.myfaces.trinidadinternal.util.ObjectInputStreamResolveClass;
import org.apache.myfaces.trinidadinternal.util.SubKeyMap;
import org.apache.myfaces.trinidadinternal.util.TokenCache;


/**
 * StateManager that handles a hybrid client/server strategy:  a
 * SerializedView is stored on the server, and only a small token
 * is stored on the client.
 * 

*

Application View cache

*

* In addition, an optional Application view cache is supported. * This view cache will, when enabled, perform special caching * of all state for non-postback requests (that is, the initial * state of all pages). For all pages, their SerializedView state * is stored in a Map at application scope, and reused across * all users. This simultaneously eliminates the expense of saving * the state at all (except for the first request for any page), * and significantly reduces memory usage as long as users are * largely viewing initial pages only. *

* In addition, because the viewId is sufficient to identify the * page state out of the cache, the token can be completely * constant across requests and users. This makes it possible * to cache the page content (which is not possible otherwise). *

* Since application scope objects do not support failover, * a mirror of the cache is saved at session scope. The mirror * is an LRU map of the last 16 application-scoped entries, but * since it stores precisely the same SerializedView instances * as the application scope, the additional memory requirements * are minimal. *

* @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 $ */ public class StateManagerImpl extends StateManagerWrapper { static public final String USE_APPLICATION_VIEW_CACHE_INIT_PARAM = "org.apache.myfaces.trinidad.USE_APPLICATION_VIEW_CACHE"; static public final String CACHE_VIEW_ROOT_INIT_PARAM = "org.apache.myfaces.trinidad.CACHE_VIEW_ROOT"; /** * Servlet context initialization parameter used by * StateManagerImpl to decide what sort of state should be saved * on the client. Valid values are "token" and "all"; the * default is "token". */ static public final String CLIENT_STATE_METHOD_PARAM_NAME = "org.apache.myfaces.trinidad.CLIENT_STATE_METHOD"; /** * Servlet context initialization parameter used by * StateManagerImpl to decide how many tokens can be stored * per user. The default is 15. */ static public final String CLIENT_STATE_MAX_TOKENS_PARAM_NAME = "org.apache.myfaces.trinidad.CLIENT_STATE_MAX_TOKENS"; /** * Servlet context initialization parameter used by * StateManagerImpl to decide whether to zip state. * Valid values are true and false */ static public final String COMPRESS_VIEW_STATE_PARAM_NAME = "org.apache.myfaces.trinidad.COMPRESS_VIEW_STATE"; /** * Value indicating that only a simple token will be stored * on the client. */ static public final String CLIENT_STATE_METHOD_TOKEN = "token"; /** * Value indicating that the entire component state will be stored * on the client. */ static public final String CLIENT_STATE_METHOD_ALL = "all"; public StateManagerImpl( StateManager delegate) { _delegate = delegate; } @Override protected StateManager getWrapped() { return _delegate; } @SuppressWarnings("deprecation") @Override public Object saveView(FacesContext context) { assert(context != null); if(isSavingStateInClient(context)) { SerializedView view = _saveSerializedView(context); return new Object[]{view.getStructure(), view.getState()}; } return super.saveView(context); } @Override @SuppressWarnings("deprecation") public SerializedView saveSerializedView(FacesContext context) { assert(context != null); if(isSavingStateInClient(context)) { return _saveSerializedView(context); } return _delegate.saveSerializedView(context); } /** * Save a component tree as an Object. */ static public Object saveComponentTree( FacesContext context, UIComponent component) { // Don't remove transient components... Object structure = new Structure(component); Object state = component.processSaveState(context); return new PageState(context, new Object[]{structure, state}, null); } /** * Take an object created by saveComponentTree() * and instantiate it as a UIComponent. */ static public UIComponent restoreComponentTree( FacesContext context, Object savedState) throws ClassNotFoundException, InstantiationException, IllegalAccessException { if (savedState == null) throw new NullPointerException(); if (!(savedState instanceof PageState)) throw new IllegalArgumentException(_LOG.getMessage( "INVALID_SAVED_STATE_OBJECT")); PageState viewState = (PageState) savedState; Object[] stateArray = (Object[])viewState.getViewState(context); Object structure = stateArray[0]; Object state = stateArray[1]; UIComponent component = ((Structure) structure).createComponent(); if (state != null) component.processRestoreState(context, state); return component; } /** * Save a view root. Doesn't return a SerializedView because * SerializedView is a non-static inner class, and this needs * to be a static method. */ static public Object saveViewRoot( FacesContext context, UIViewRoot root) { _removeTransientComponents(root); Object structure = new Structure(root); Object state = root.processSaveState(context); return new PageState(context, new Object[]{structure, state}, root); } static public UIViewRoot restoreViewRoot( FacesContext context, Object saved) throws ClassNotFoundException, InstantiationException, IllegalAccessException { if (saved == null) throw new NullPointerException(); PageState viewState = (PageState) saved; UIViewRoot root = viewState.popRoot(context); if (root != null) { return root; // bug 4712492 } Object[] stateArray = (Object[])viewState.getViewState(context); Object structure = stateArray[0]; Object state = stateArray[1]; root = (UIViewRoot) ((Structure) structure).createComponent(); if (state != null) root.processRestoreState(context, state); return root; } @SuppressWarnings({"unchecked", "deprecation"}) private SerializedView _saveSerializedView(FacesContext context) { SerializedView view = _getCachedSerializedView(context); if (view != null) return view; UIViewRoot root = context.getViewRoot(); boolean dontSave = false; // See if we're going to use the application view cache for // this request Map applicationViewCache = null; Map perSessionApplicationViewCache = null; if (_useApplicationViewCache(context)) { // OK, we are: so find the application cache and // the per-session mirror applicationViewCache = _getApplicationViewCache(context); perSessionApplicationViewCache = _getPerSessionApplicationViewCache(context); synchronized (applicationViewCache) { // If we've already got a copy of the state stored, then // we just need to make sure it's mirrored on the session PageState applicationState = applicationViewCache.get(root.getViewId()); if (applicationState != null) { // Note that we've got no work to do... dontSave = true; perSessionApplicationViewCache.put(root.getViewId(), applicationState); } } } _removeTransientComponents(root); Object structure = (dontSave || !_needStructure(context)) ? null : new Structure(root); Object state = dontSave ? null : root.processSaveState(context); if (_saveAsToken(context)) { String token; ExternalContext extContext = context.getExternalContext(); if (applicationViewCache == null) { assert(!dontSave); RequestContext trinContext = RequestContext.getCurrentInstance(); TokenCache cache = _getViewCache(trinContext, extContext); assert(cache != null); Map sessionMap = extContext.getSessionMap(); // get view cache key with "." separator suffix to separate the SubKeyMap keys String subkey = _getViewCacheKey(extContext, trinContext, _SUBKEY_SEPARATOR); Map stateMap = new SubKeyMap(sessionMap, subkey); // Sadly, we can't save just a SerializedView, because we should // save a serialized object, and SerializedView is a *non*-static // inner class of StateManager PageState pageState = new PageState( context, new Object[]{structure, state}, // Save the view root into the page state as a transient // if this feature has not been disabled _useViewRootCache(context) ? root : null); String requestToken = _getRequestTokenForResponse(context); // If we have a cached token that we want to reuse, // and that token hasn't disappeared from the cache already // (unlikely, but not impossible), use the stateMap directly // without asking the cache for a new token if ((requestToken != null) && cache.isAvailable(requestToken)) { // NOTE: under *really* high pressure, the cache might // have been emptied between the isAvailable() call and // this put(). This seems sufficiently implausible to // be worth punting on stateMap.put(requestToken, pageState); token = requestToken; // NOTE 2: we have not pinned this reused state to any old state // This is OK for current uses of pinning and state reuse, // as pinning stays constant within a window, and we're not // erasing pinning at all. } else { // See if we should pin this new state to any old state String pinnedToken = (String)extContext.getRequestMap().get(_PINNED_STATE_TOKEN_KEY); token = cache.addNewEntry(pageState, stateMap, pinnedToken); } // clear out all of the previous PageStates' UIViewRoots and add this page // state as an active page state. This is necessary to avoid UIViewRoots // laying around if the user navigates off of a page using a GET synchronized(extContext.getSession(true)) { // get the per-window key for the active page state. We only store the token rather than // the view state itself here in order to keep fail-over Serialization from Serializing this // state twice, once where it appears here and the second time in the token map itself // See Trinidad-1779 String activePageStateKey = _getActivePageTokenKey(extContext, trinContext); String activeToken = (String)sessionMap.get(activePageStateKey); // we only need to clear out the state if we're actually changing pages and thus tokens. // Since we have already updated the state for if (!token.equals(activeToken)) { if (activeToken != null) { PageState activePageState = stateMap.get(activeToken); if (activePageState != null) activePageState.clearViewRootState(); } sessionMap.put(activePageStateKey, token); } } } // If we got the "applicationViewCache", we're using it. else { // use null viewRoot since this state is shared across users: PageState applicationState = new PageState(context, new Object[]{structure, state}, null); // If we need to, stash the state off in our cache if (!dontSave) { synchronized (applicationViewCache) { applicationViewCache.put(root.getViewId(), applicationState); perSessionApplicationViewCache.put(root.getViewId(), applicationState); } } token = _APPLICATION_CACHE_TOKEN; } assert(token != null); // Create a "tokenView" which abuses SerializedView to store // our token only view = new SerializedView(token, null); // And store the token for this request extContext.getRequestMap().put(_REQUEST_STATE_TOKEN_KEY, token); } else { assert(!dontSave); view = new SerializedView(structure, state); } _saveCachedSerializedView(context, view); return view; } /** * Requests that an old state token be "pinned" to the state of * the current request. This means that the view state corresponding * to the token will not be released before the state for this request * is released. */ @SuppressWarnings("unchecked") static public void pinStateToRequest(FacesContext context, String stateToken) { context.getExternalContext().getRequestMap().put( _PINNED_STATE_TOKEN_KEY, stateToken); } /** * @return the state token for the current request */ static public String getStateToken(FacesContext context) { return (String) context.getExternalContext().getRequestMap().get( _REQUEST_STATE_TOKEN_KEY); } /** * Mark the the incoming request token should be used for the response */ @SuppressWarnings("unchecked") static public void reuseRequestTokenForResponse(ExternalContext ec) { ec.getRequestMap().put(_REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY, Boolean.TRUE); } /** * Clears the flag indicating that the old request token should be used for the response. */ @SuppressWarnings("unchecked") static public void clearReuseRequestTokenForResponse(ExternalContext ec) { ec.getRequestMap().remove(_REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY); } /** * If we've been asked to reuse the request token for the response, * store it off. */ @SuppressWarnings("unchecked") static private void _updateRequestTokenForResponse( FacesContext context, String token) { Map requestMap = context.getExternalContext().getRequestMap(); // Go from TRUE -> the saved token if (Boolean.TRUE.equals( requestMap.get(_REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY))) { requestMap.put(_REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY, token); } } /** * Get any cached token for the response. */ @SuppressWarnings("unchecked") static private String _getRequestTokenForResponse( FacesContext context) { Map requestMap = context.getExternalContext().getRequestMap(); Object token = requestMap.get(_REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY); // We wanted to, but didn't have anything saved if (Boolean.TRUE.equals(token)) return null; return (String) token; } @Override @SuppressWarnings("deprecation") public void writeState(FacesContext context, SerializedView state) throws IOException { _delegate.writeState(context, state); } @SuppressWarnings({"unchecked", "deprecation"}) @Override public UIViewRoot restoreView(FacesContext context, String viewId, String renderKitId) { final ExternalContext extContext = context.getExternalContext(); // If we're being asked to execute a "return" event from, say, a dialog, always // restore the "launch view", which was set over in the TrinidadFilter. UIViewRoot launchView = (UIViewRoot) extContext.getRequestMap().remove(RequestContextImpl.LAUNCH_VIEW); if (launchView != null) { TrinidadPhaseListener.markPostback(context); return launchView; } if (!isSavingStateInClient(context)) return _delegate.restoreView(context, viewId, renderKitId); final Object structure; final Object state; boolean recalculateLocale = false; ResponseStateManager rsm = _getResponseStateManager(context, renderKitId); if (_saveAsToken(context)) { Object token = rsm.getTreeStructureToRestore(context, viewId); if (token == null) { _LOG.finest("No token in the request for view \"{0}\"; probably a first view.", viewId); return null; } assert(token instanceof String); _LOG.finer("Restoring saved view state for token {0}", token); PageState viewState; // Load from the application cache if (_APPLICATION_CACHE_TOKEN.equals(token)) { Map cache = _getApplicationViewCache(context); Map perSessionCache = _getPerSessionApplicationViewCache(context); // Synchronize on the application-level cache. // =-=AEW This may produce excessive contention synchronized (cache) { // Look first in the per-session cache viewState = perSessionCache.get(viewId); if (viewState == null) { // Nope, it's not there. Look in the application cache viewState = cache.get(viewId); // And if we find it there, then push it back into // the per-session cache (it may have expired) if (viewState != null) perSessionCache.put(viewId, viewState); } // If the view was found in the application cache then we // know it would be unsafe to use its locale for this session. // Same conclusion, however, even if found in the per-session // cache, since the latter is just a mirror of the former. recalculateLocale = true; } } else { RequestContext trinContext = RequestContext.getCurrentInstance(); // get view cache key with "." separator suffix to separate the SubKeyMap keys String subkey = _getViewCacheKey(extContext, trinContext, _SUBKEY_SEPARATOR); Map stateMap = new SubKeyMap( extContext.getSessionMap(), subkey); viewState = stateMap.get(token); if (viewState != null) _updateRequestTokenForResponse(context, (String) token); // Make sure that if the view state is present, the cache still // has the token, and vice versa // NOTE: it's very important that we call through to the // token cache here, not just inside the assert. If we don't, // then we don't actually access the token, so it doesn't // get bumped up to the front in the LRU Cache! boolean isAvailable = _getViewCache(trinContext, extContext).isAvailable((String) token); assert ((viewState != null) == isAvailable); } if (viewState == null) { _LOG.severe("CANNOT_FIND_SAVED_VIEW_STATE", token); return null; } _LOG.fine("Successfully found view state for token {0}", token); UIViewRoot root = viewState.popRoot(context); // bug 4712492 if (root != null) { _LOG.finer("UIViewRoot for token {0} already exists. Bypassing restoreState", token); return root; } Object[] stateArray = (Object[])viewState.getViewState(context); structure = stateArray[0]; state = stateArray[1]; } else { structure = rsm.getTreeStructureToRestore(context, viewId); state = rsm.getComponentStateToRestore(context); } if (structure == null) { UIViewRoot root = context.getViewRoot(); if (root == null && _needStructure(context)) { _LOG.severe("NO_STRUCTURE_ROOT_AVAILABLE"); return null; } if (state != null) root.processRestoreState(context, state); return root; } else { if (!(structure instanceof Structure)) { _LOG.severe("NO_STRUCTURE_AVAILABLE"); return null; } // OK, we've structure and state; let's see what we can do! try { UIViewRoot root = (UIViewRoot) ((Structure) structure).createComponent(); if (state != null) root.processRestoreState(context, state); if (recalculateLocale) { // Ensure that locale gets re-calculated when next fetched. root.setLocale((Locale) null); } _LOG.finer("Restored state for view \"{0}\"", viewId); return root; } catch (ClassNotFoundException cnfe) { _LOG.severe(cnfe); } catch (InstantiationException ie) { _LOG.severe(ie); } catch (IllegalAccessException iae) { _LOG.severe(iae); } } return null; } /** * The given parameter (perViewStateSaving) indicates * if we need to enable client- OR server-side state-saving * for the current VIEW. * *

* This is an internal method, that is ONLY called by the * Trinidad Document *

* * @param perViewStateSaving default, server or client for stateSaving */ public void setPerViewStateSaving(String perViewStateSaving) { // tweak the given value into one of the three possible enums // TODO: catch wrong/invalid values (aka baby sitting) Map attrs = FacesContext.getCurrentInstance().getExternalContext().getRequestMap(); attrs.put(_PER_PAGE_STATE_SAVING, StateSaving.valueOf(perViewStateSaving.toUpperCase())); } @Override public boolean isSavingStateInClient(FacesContext context) { return _delegate.isSavingStateInClient(context); } // // Protected APIs: we don't want // @Override protected Object getTreeStructureToSave(FacesContext context) { throw new UnsupportedOperationException(); } @Override protected Object getComponentStateToSave(FacesContext context) { throw new UnsupportedOperationException(); } @Override protected UIViewRoot restoreTreeStructure (FacesContext context, String viewId, String renderKitId) { throw new UnsupportedOperationException(); } @Override protected void restoreComponentState (FacesContext context, UIViewRoot viewRoot, String renderKitId) { throw new UnsupportedOperationException(); } /** * Returns the TokenCache for the current window * @param trinContext * @param extContext * @return */ private TokenCache _getViewCache(RequestContext trinContext, ExternalContext extContext) { String cacheKey = _getViewCacheKey(extContext, trinContext, null); return TokenCache.getTokenCacheFromSession(extContext,cacheKey, true,_getCacheSize(extContext)); } /** * Returns a key suitable for finding the per-window active page state key * @param extContext * @param trinContext * @return */ static private String _getActivePageTokenKey( ExternalContext extContext, RequestContext trinContext) { return _getPerWindowCacheKey(extContext, trinContext, _ACTIVE_PAGE_TOKEN_SESSION_KEY, null); } /** * Returns a key suitable for finding the per-window cache key * @param extContext * @param trinContext * @param suffix * @return */ static private String _getViewCacheKey( ExternalContext extContext, RequestContext trinContext, Character suffix) { return _getPerWindowCacheKey(extContext, trinContext, _VIEW_CACHE_KEY, suffix); } /** * Returns a key of the form . if a window and a suffix are available * . if just a window is available * if neither a window or a suffix is available * @param eContext * @param trinContext * @param prefix * @param suffix * @return */ static private String _getPerWindowCacheKey( ExternalContext eContext, RequestContext trinContext, String prefix, Character suffix) { Window currWindow = trinContext.getWindowManager().getCurrentWindow(eContext); // if we have a current window or a suffix, we need a StringBuilder to calculate the cache key if ((currWindow != null) || (suffix != null)) { // get the window id and the extra size neeeded to store it and its separator String windowId; int windowPartSize; if (currWindow != null) { windowId = currWindow.getId(); // add 1 for separator windowPartSize = windowId.length() + 1; } else { windowId = null; windowPartSize = 0; } int builderSize = prefix.length() + windowPartSize; // add extra space for the suffix Character if (suffix != null) builderSize += 1; // add the constant part to the StringBuilder StringBuilder keyBuilder = new StringBuilder(builderSize); keyBuilder.append(prefix); // add the windowId and its separator if (currWindow != null) { keyBuilder.append('.'); keyBuilder.append(windowId); } // add the suffix if any if (suffix != null) keyBuilder.append(suffix); return keyBuilder.toString(); } else { return prefix; } } /** * Tests whether to send a small string token, or the entire * serialized component state to the client-side. * @return true, if the small string token is to be sent to the client-side. */ private boolean _saveAsToken(FacesContext context) { // the setPerViewStateSaving() method stores the PER_PAGE_STATE_SAVING value on the // request attribute map, during rendering Map attrMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap(); StateSaving stateSaving = (StateSaving) attrMap.get(_PER_PAGE_STATE_SAVING); // if the 'stateSaving' attribute said "client" we need to return FALSE // in order to do "full" client-side-state saving. Boolean forceStateSavingPerView = null; if (StateSaving.CLIENT.equals(stateSaving)) { forceStateSavingPerView = Boolean.FALSE; } // for "server" we return TRUE here, as we want client-side // state-saving to be turned OFF, regardless of the configuration // settings. else if (StateSaving.SERVER.equals(stateSaving)) { forceStateSavingPerView = Boolean.TRUE; } // for the stateSaving "defaul" we just let go and do what it // normally would do... if (forceStateSavingPerView != null) { return forceStateSavingPerView.booleanValue(); } ExternalContext external = context.getExternalContext(); Object stateSavingMethod = external.getInitParameterMap().get(StateManager.STATE_SAVING_METHOD_PARAM_NAME); // on "SERVER" state-saving we return TRUE, since we want send down a token string. if ((stateSavingMethod == null) || StateManager.STATE_SAVING_METHOD_SERVER.equalsIgnoreCase((String) stateSavingMethod)) { return true; } // if the user set state-saving to "CLIENT" *and* the client-state-method to "ALL" // we return FALSE, since we want to save the ENTIRE state on the client... Object clientMethod = external.getInitParameterMap().get(CLIENT_STATE_METHOD_PARAM_NAME); if ((clientMethod != null) && CLIENT_STATE_METHOD_ALL.equalsIgnoreCase((String) clientMethod)) { return false; } // if the user has used the 'stateSaving' attribute to specify // client, we force the state mananger (see above) to render the entire // state on the client. The indicator is stashed on the FacesContext and // is therefore NOT visible during "restoreView" phase. So if we reach this point // here AND we are using "full" client-side-state-saving, this has been tweaked // on the previous page rendering phase... // In this case we return FALSE to indicate to restore the entire (serialized) // state from the client! // // @see setPerViewStateSaving() String viewStateValue = external.getRequestParameterMap().get(ResponseStateManager.VIEW_STATE_PARAM); if (! (_isViewStateNull(viewStateValue) || viewStateValue.startsWith("!"))) { return false; } // is vanilla JSF used? No Trinidad render-kit-id give? If so, we need to return FALSE, // since we want to save the ENTIRE state on the client... UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot(); if (viewRoot != null && RenderKitFactory.HTML_BASIC_RENDER_KIT.equals(viewRoot.getRenderKitId())) { return false; } // Last missing option: state-saving is "CLIENT" and the client-state-method uses // its default (token), so we return TRUE to send down a token string. return true; } /** * One oddity with some of the JSF 1.2 Bridge impelmentations is that when * there is a goLink that is encoded as an action URL, as the are expected to * be, the bridge works around an issue in Mojarra that would cause JSF to not * display properly. The reason for this is that ActionRequests into the * Portal are considered "posts" and Mojarra will skip the render phase of JSF * if there is a post request without a viewStateParameter. Hopefully this * issue can be fixed in later versions of Mojarra 1.2 and it DOES NOT exist * in MyFaces or Mojarra 2.0 and later. * * For now, however, in the JSF 1.2 branches, we need to check to see if the * VIEW_STATE_PARAMETER is "org.apache.myfaces.portlet.faces.nullViewState" * in addition to the actual Null value. This bug is being tracked in * Jira: PORTLETBRIDGE-216. * * @param ec * @return */ private boolean _isViewStateNull(String viewStateValue) { return viewStateValue == null || "org.apache.myfaces.portlet.faces.nullViewState".equals(viewStateValue); } private int _getCacheSize(ExternalContext extContext) { Object maxTokens = extContext.getInitParameterMap().get(CLIENT_STATE_MAX_TOKENS_PARAM_NAME); if (maxTokens != null) { try { return Math.max(1, Integer.parseInt((String) maxTokens)); } catch (NumberFormatException nfe) { _LOG.warning("Ignoring servlet init parameter:"+CLIENT_STATE_MAX_TOKENS_PARAM_NAME+ "\n unable to parse:"+maxTokens, nfe); _LOG.warning(nfe); } } return _DEFAULT_CACHE_SIZE; } // // @todo Map is a bad structure // @todo a static size is bad @SuppressWarnings("unchecked") static private Map _getApplicationViewCache(FacesContext context) { synchronized (_APPLICATION_VIEW_CACHE_LOCK) { Map appMap = context.getExternalContext().getApplicationMap(); Map cache = (Map)appMap.get(_APPLICATION_VIEW_CACHE_KEY); if (cache == null) { cache = new HashMap(128); appMap.put(_APPLICATION_VIEW_CACHE_KEY, cache); } return cache; } } @SuppressWarnings("unchecked") static private Map _getPerSessionApplicationViewCache(FacesContext context) { ExternalContext external = context.getExternalContext(); Object session = external.getSession(true); assert(session != null); Map cache; // Synchronize on the session object to ensure that // we don't ever create two different caches synchronized (session) { Map sessionMap = external.getSessionMap(); cache = (Map) sessionMap.get(_APPLICATION_VIEW_CACHE_KEY); if (cache == null) { cache = _createPerSessionApplicationViewCache(); sessionMap.put(_APPLICATION_VIEW_CACHE_KEY, cache); } } return cache; } // // For the per-session mirror of the application view cache, // use an LRU LinkedHashMap to store the latest 16 pages. // static private Map _createPerSessionApplicationViewCache() { return new LRUCache(_MAX_PER_SESSION_APPLICATION_SIZE); } static private final int _MAX_PER_SESSION_APPLICATION_SIZE = 16; // // Use the application view cache if and only if: // (1) We're saving state tokens on the client // (2) This is *not* a postback request // (3) The feature has been explicitly enabled // private boolean _useApplicationViewCache(FacesContext context) { if (_useApplicationViewCache == Boolean.FALSE) return false; if (_saveAsToken(context) && // Note: do not use TrinidadPhaseListener, as that // will return "true" even after navigation has occured, // but the Application View Cache is still fine. //!TrinidadPhaseListener.isPostback(context) !RequestContext.getCurrentInstance().isPostback()) { if (_useApplicationViewCache == null) { String s = context.getExternalContext().getInitParameter( USE_APPLICATION_VIEW_CACHE_INIT_PARAM); _useApplicationViewCache = "true".equalsIgnoreCase(s) ? Boolean.TRUE : Boolean.FALSE; } if (Boolean.TRUE.equals(_useApplicationViewCache)) { _LOG.severe("USE_APPLICATION_VIEW_CACHE_UNSUPPORTED"); } return _useApplicationViewCache.booleanValue(); } return false; } private boolean _useViewRootCache(FacesContext context) { if (_useViewRootCache == null) { String s = context.getExternalContext().getInitParameter( CACHE_VIEW_ROOT_INIT_PARAM); _useViewRootCache = (!"false".equalsIgnoreCase(s)) ? Boolean.TRUE : Boolean.FALSE; } return _useViewRootCache.booleanValue(); } private boolean _needStructure(FacesContext context) { if (_structureGeneratedByTemplate == null) { ExternalContext external = context.getExternalContext(); String restoreMode = external.getInitParameter( FaceletViewHandler.PARAM_BUILD_BEFORE_RESTORE); if ("true".equals(restoreMode)) _structureGeneratedByTemplate = Boolean.TRUE; else _structureGeneratedByTemplate = Boolean.FALSE; } return !_structureGeneratedByTemplate.booleanValue(); } static private ResponseStateManager _getResponseStateManager( FacesContext context, String renderKitId) { RenderKitFactory factory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY); RenderKit kit = factory.getRenderKit(context, renderKitId); return kit.getResponseStateManager(); } @SuppressWarnings("unchecked") static private void _removeTransientComponents( UIComponent root) { List components = new ArrayList(); _gatherTransientComponents(root, components); Iterator iter = components.iterator(); while (iter.hasNext()) { UIComponent kid = iter.next(); UIComponent parent = kid.getParent(); // First, see if its a child if (parent.getChildCount() > 0) { List children = parent.getChildren(); if (children.remove(kid)) { continue; } } // Nope, guess it's a facet // 2006-08-02: -= Simon Lessard // Not 1.5 structure and inefficient loop // values() is more efficient as you don't have // to do a second lookup for the value. Map facets = parent.getFacets(); for(Iterator facetIter = facets.values().iterator(); facetIter.hasNext();) { if(facetIter.next() == kid) { facetIter.remove(); // FIXME: -= Simon Lessard // Is that continue need to labeled to go all the way up to // the first while? Currently it won't cause any problem, but // it's a performance loss. continue; } } // Couldn't find the component at all in its parent: that's // not good. assert false; } } @SuppressWarnings("unchecked") static private void _gatherTransientComponents( UIComponent component, List componentsToRemove) { Iterator kids = component.getFacetsAndChildren(); while (kids.hasNext()) { UIComponent kid = kids.next(); // UIXComponentBase doesn't mind transient components // in its saved state, so don't bother with this. if (!(component instanceof UIXComponentBase) && kid.isTransient()) { componentsToRemove.add(kid); } else { _gatherTransientComponents(kid, componentsToRemove); } } } @SuppressWarnings("deprecation") private SerializedView _getCachedSerializedView( FacesContext context) { return (SerializedView) context.getExternalContext(). getRequestMap().get(_CACHED_SERIALIZED_VIEW); } @SuppressWarnings({"unchecked","deprecation"}) private void _saveCachedSerializedView( FacesContext context, SerializedView state) { context.getExternalContext().getRequestMap().put(_CACHED_SERIALIZED_VIEW, state); } private static final class ViewRootState { public ViewRootState(FacesContext context, UIViewRoot viewRoot) { if (viewRoot == null) throw new NullPointerException(); _viewRoot = viewRoot; _viewRootState = viewRoot.saveState(context); } public UIViewRoot getViewRoot() { return _viewRoot; } public Object getViewRootState() { return _viewRootState; } private final UIViewRoot _viewRoot; private final Object _viewRootState; } private static final class PageState implements Serializable { private static final long serialVersionUID = 1L; private Object _viewState; // use transient since UIViewRoots are not Serializable. private transient ViewRootState _cachedState; public PageState(FacesContext fc, Object viewState, UIViewRoot root) { _viewState = viewState; boolean zipState = _zipState(fc); if (zipState || StateUtils.checkComponentTreeStateSerialization(fc)) { if (zipState) { // zip the page state. This will also catch any serialization problems. _zipToBytes(fc, viewState); } else { // if component tree serialization checking is on (in order to validate // fail over support, attempt to Serialize all of the component state // immediately try { new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(viewState); } catch (IOException e) { throw new RuntimeException(_LOG.getMessage("COMPONENT_TREE_SERIALIZATION_FAILED"), e); } } } // we need this state, as we are going to recreate the UIViewRoot later. see // the popRoot() method: _cachedState = (root != null) ? new ViewRootState(fc, root) : null; } public Object getViewState(FacesContext context) { if (_zipState(context)) { return _unzipBytes(context, (byte[])_viewState); } return _viewState; } public void clearViewRootState() { synchronized(this) { _cachedState = null; } } @SuppressWarnings("unchecked") public UIViewRoot popRoot(FacesContext fc) { UIViewRoot root = null; Object viewRootState = null; // we need to synchronize because we are mutating _root // which is shared between simultaneous requests from the same user: synchronized(this) { if (_cachedState != null) { root = _cachedState.getViewRoot(); viewRootState = _cachedState.getViewRootState(); // we must clear the cached viewRoot. This is because UIComponent trees // are mutable and if the back button // is used to come back to an old PageState, then it would be // really bad if we reused that component tree: _cachedState = null; } } if (root != null) { // If an error happens during updateModel, JSF 1.1 does not // clear FacesEvents (or FacesMessages, IIRC), so any pending // events will still be present, on the subsequent request. // so to clear the events, we create a new UIViewRoot. // must get the UIViewRoot from the application so that // we pick up any custom ViewRoot defined in faces-config.xml: UIViewRoot newRoot = (UIViewRoot) fc.getApplication().createComponent(UIViewRoot.COMPONENT_TYPE); //This code handles automatic namespacing in a JSR-301 environment if(ExternalContextUtils.isPortlet(fc.getExternalContext())) { //IMPORTANT: To avoid introducing a runtime dependency on the bridge, //this method should only be executed when we have a portlet //request. try { newRoot = (UIViewRoot) root.getClass().newInstance(); } catch (InstantiationException e) { _LOG.finest("Unable to instantiate new root of type class \"{0}\".", root.getClass()); } catch (IllegalAccessException e) { _LOG.finest("IllegalAccessException on new root of type class \"{0}\".", root.getClass()); } } // must call restoreState so that we setup attributes, listeners, // uniqueIds, etc ... newRoot.restoreState(fc, viewRootState); // we need to use a temp list because as a side effect of // adding a child to a UIComponent, that child is removed from // the parent UIComponent. So the following will break: // newRoot.getChildren().addAll(root.getChildren()); // because "root"'s child List is being mutated as the List // is traversed. List temp = new ArrayList(root.getChildCount()); temp.addAll(root.getChildren()); newRoot.getChildren().addAll(temp); return newRoot; } return null; } private boolean _zipState(FacesContext fc) { // default is false Object zipStateObject = fc.getExternalContext().getInitParameter(COMPRESS_VIEW_STATE_PARAM_NAME); if (zipStateObject == null) return false; return zipStateObject.toString().equalsIgnoreCase("true"); } private Object _unzipBytes(FacesContext context, byte[] zippedBytes) { Inflater decompressor = null; ExternalContext externalContext = context.getExternalContext(); Map sessionMap = externalContext.getSessionMap(); try { //Get inflater from session cope TransientHolder th = (TransientHolder)sessionMap.remove("PAGE_STATE_INFLATER"); if (th != null) { decompressor = th.getValue(); } if(decompressor == null) { decompressor = new Inflater(); } decompressor.setInput(zippedBytes); ByteArrayOutputStream bos = new ByteArrayOutputStream(zippedBytes.length); byte[] buf = new byte[zippedBytes.length*5]; while (!decompressor.finished()) { try { int count = decompressor.inflate(buf); bos.write(buf, 0, count); } catch (DataFormatException e) { throw new RuntimeException(_LOG.getMessage("UNZIP_STATE_FAILED"), e); } } ByteArrayInputStream baos = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStreamResolveClass(baos); Object unzippedState = ois.readObject(); ois.close(); return unzippedState; } catch(ClassNotFoundException cnfe) { throw new RuntimeException(_LOG.getMessage("UNZIP_STATE_FAILED"), cnfe); } catch(IOException ioe) { throw new RuntimeException(_LOG.getMessage("UNZIP_STATE_FAILED"), ioe); } finally { //Reset and put back if(decompressor != null) { decompressor.reset(); decompressor.setInput(_EMPTY); TransientHolder th = TransientHolder.newTransientHolder(decompressor); sessionMap.put("PAGE_STATE_INFLATER", th); } } } private void _zipToBytes(FacesContext context, Object viewState) { Deflater compresser = null; ExternalContext externalContext = context.getExternalContext(); Map sessionMap = externalContext.getSessionMap(); try { //Get deflater from session cope TransientHolder th = (TransientHolder)sessionMap.remove("PAGE_STATE_DEFLATER"); if (th != null) { compresser = th.getValue(); } if(compresser == null) { compresser = new Deflater(Deflater.BEST_SPEED); } //Serialize state ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(viewState); oos.flush(); oos.close(); byte[] ret = baos.toByteArray(); compresser.setInput(ret); compresser.finish(); baos.reset(); byte[] buf = new byte[ret.length/5]; while (!compresser.finished()) { int count = compresser.deflate(buf); baos.write(buf, 0, count); } _viewState = baos.toByteArray(); } catch (IOException e) { throw new RuntimeException(_LOG.getMessage("ZIP_STATE_FAILED"), e); } finally { //Reset and put back if(compresser != null) { compresser.reset(); compresser.setInput(_EMPTY); TransientHolder th = TransientHolder.newTransientHolder(compresser); sessionMap.put("PAGE_STATE_DEFLATER", th); } } } } /** * Static ENUM to capture the values of the 's * 'stateSaving' attribute */ static private enum StateSaving { DEFAULT(CoreDocument.STATE_SAVING_DEFAULT), CLIENT(CoreDocument.STATE_SAVING_CLIENT), SERVER(CoreDocument.STATE_SAVING_SERVER); StateSaving(String stateSaving) { _stateSaving = stateSaving; } private String _stateSaving; } private final StateManager _delegate; private Boolean _useViewRootCache; private Boolean _useApplicationViewCache; private Boolean _structureGeneratedByTemplate; private static final Character _SUBKEY_SEPARATOR = new Character('.'); private static final int _DEFAULT_CACHE_SIZE = 15; private static final Object _APPLICATION_VIEW_CACHE_LOCK = new Object(); // base key used to identify the view cache. The window name, if any, is appended to this private static final String _VIEW_CACHE_KEY = "org.apache.myfaces.trinidadinternal.application.VIEW_CACHE"; private static final String _APPLICATION_VIEW_CACHE_KEY = "org.apache.myfaces.trinidadinternal.application.APPLICATION_VIEW_CACHE"; // key to stash the per_page_state_saving during rendering private static final String _PER_PAGE_STATE_SAVING = "org.apache.myfaces.trinidadimpl.PER_PAGE_STATE_SAVING"; private static final String _CACHED_SERIALIZED_VIEW = "org.apache.myfaces.trinidadinternal.application.CachedSerializedView"; private static final String _REQUEST_STATE_TOKEN_KEY = "org.apache.myfaces.trinidadinternal.application.REQUEST_STATE_TOKEN"; private static final String _PINNED_STATE_TOKEN_KEY = "org.apache.myfaces.trinidadinternal.application.PINNED_STATE_TOKEN"; private static final String _REUSE_REQUEST_TOKEN_FOR_RESPONSE_KEY = "org.apache.myfaces.trinidadinternal.application.REUSE_REQUEST_TOKEN_FOR_RESPONSE"; // key for saving the token to the PageState for the last accessed view in this Session private static final String _ACTIVE_PAGE_TOKEN_SESSION_KEY = "org.apache.myfaces.trinidadinternal.application.StateManagerImp.ACTIVE_PAGE_STATE"; private static final String _APPLICATION_CACHE_TOKEN = "_a_"; private static final long serialVersionUID = 1L; private static final byte[] _EMPTY = new byte[0]; private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(StateManagerImpl.class); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy