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 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.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
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 javax.faces.view.StateManagementStrategy;
import javax.faces.view.ViewDeclarationLanguage;

import javax.servlet.http.HttpSession;

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.context.WindowManager;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.util.ClassLoaderUtils;
import org.apache.myfaces.trinidad.util.ExternalContextUtils;
import org.apache.myfaces.trinidad.util.ref.PseudoReference;
import org.apache.myfaces.trinidad.util.ref.PseudoReferenceFactory;
import org.apache.myfaces.trinidad.util.ref.SoftPseudoReferenceFactory;
import org.apache.myfaces.trinidad.util.ref.StrongPseudoReferenceFactory;
import org.apache.myfaces.trinidadinternal.context.RequestContextImpl;
import org.apache.myfaces.trinidadinternal.context.TrinidadPhaseListener;
import org.apache.myfaces.trinidadinternal.util.ObjectInputStreamResolveClass;
import org.apache.myfaces.trinidadinternal.util.SubKeyMap;
import org.apache.myfaces.trinidadinternal.util.TokenCache;
import org.apache.myfaces.trinidadinternal.util.TokenCacheDebugUtils;


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

* @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 { // TODO this should be removed, see comments in restoreView where this constant is used // on why this is needed public static final String RESPONSE_STATE_MANAGER_STATE_KEY = "org.apache.myfaces.trinidadinternal.application.StateManagerImp.RESPONSE_STATE_MANAGER_STATE"; 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 public StateManager getWrapped() { return _delegate; } @Override public String getViewState(FacesContext context) { Object state = saveView(context); if (state != null) { return context.getRenderKit().getResponseStateManager().getViewState(context,state); } else { return null; } } private boolean _useViewRootCache(FacesContext context, RequestContext trinContext) { return _getOrCreateViewRootStateRefFactory(context, trinContext) != null; } @SuppressWarnings("deprecation") @Override public Object saveView(FacesContext context) { assert(context != null); // see if a view has been saved on the request Object viewState = _getCachedViewState(context); if (viewState != null) { // TODO gcrawfor // when is this not null, meaning when is saveView being called multiple times // per request? return viewState; } // if the root is transient don't state save UIViewRoot viewRoot = context.getViewRoot(); if (viewRoot.isTransient()) { return null; } String viewId = viewRoot.getViewId(); StateManagementStrategy sms = _getStateManagementStrategy(context, viewId); Map contextAttributes = context.getAttributes(); try { // TODO Once we're dependent on JSF 2.1 we should be using StateManager.IS_SAVING_STATE as the key, but // for now we can use the String value of StateManager.IS_SAVING_STATE contextAttributes.put("javax.faces.IS_SAVING_STATE", Boolean.TRUE); if (sms != null) { // Force view root to use full state saving // This is necessary because we recreate the view root on postback when view root caching // is enabled and assume that that we can apply the full state if (_useViewRootCache(context, RequestContext.getCurrentInstance())) { viewRoot.clearInitialState(); } viewState = sms.saveView(context); } else { // if there's no stateManagementStrategy handle saving the state ourselves _removeTransientComponents(viewRoot); Object structure = !_needStructure(context) ? null : new Structure(viewRoot); Object state = viewRoot.processSaveState(context); viewState = new Object[]{structure, state}; } } finally { // TODO Once we're dependent on JSF 2.1 we should be using StateManager.IS_SAVING_STATE as the key, but // for now we can use the String value of StateManager.IS_SAVING_STATE contextAttributes.remove("javax.faces.IS_SAVING_STATE"); } if (_saveAsToken(context, false)) { viewState = _saveStateToCache(context, viewState, viewRoot); } _saveCachedViewState(context, viewState); return viewState; } /** * 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, _getViewRootStateRefFactory(context, RequestContext.getCurrentInstance()), 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, _getViewRootStateRefFactory(context, RequestContext.getCurrentInstance()), 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; } private Object _saveStateToCache(FacesContext context, Object viewState, UIViewRoot root) { ExternalContext extContext = context.getExternalContext(); RequestContext trinContext = RequestContext.getCurrentInstance(); TokenCache cache = _getViewCache(trinContext, extContext); assert(cache != null); // get per window view cache key with "." separator suffix to separate the SubKeyMap keys String subkey = _getViewCacheKey(extContext, trinContext, _SUBKEY_SEPARATOR); Map sessionMap = extContext.getSessionMap(); SubKeyMap 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, _getOrCreateViewRootStateRefFactory(context, trinContext), viewState, // Save the view root into the page state as a transient // if this feature has not been disabled root); String requestToken = _getRequestTokenForResponse(context); String token; // 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); } assert(token != null); // And store the token for this request extContext.getRequestMap().put(_REQUEST_STATE_TOKEN_KEY, token); // clear out the view root cache, passing in the session key for the new active state String newActivePageStateKey = stateMap.getBaseKey(token); _clearViewRootCache(extContext, newActivePageStateKey); // Create a "tokenView" which abuses state to store // our token only return new Object[]{token, null}; } private void _clearViewRootCache(ExternalContext extContext, String newActivePageStateKey) { Map sessionMap = extContext.getSessionMap(); // 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 session key under which we will store the session key of the PageState object holding // the cached UIViewRoot. We can either store one cached UIViewRoot per window or one // UIViewRoot for the entire session. // // 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 keyToActivePageStateKey = (_CACHE_VIEW_ROOT_PER_WINDOW) ? _getActivePageTokenKey(extContext, RequestContext.getCurrentInstance()) : _ACTIVE_PAGE_TOKEN_SESSION_KEY; String oldActivePageStateKey = (String)sessionMap.get(keyToActivePageStateKey); // we only need to clear out the UIViewRoot state if we're actually changing pages and thus tokens. if (!newActivePageStateKey.equals(oldActivePageStateKey)) { if (oldActivePageStateKey != null) { PageState activePageState = (PageState)sessionMap.get(oldActivePageStateKey); if (activePageState != null) { activePageState.clearViewRootState(); } } sessionMap.put(keyToActivePageStateKey, newActivePageStateKey); } } } // controls whether we are caching UIViewRoots per window. If SoftReferences aren't being used // to hang onto the UIViewRoots, we definitely want this false. If SoftReferences are used, setting // this true and letting the VM handle releasing the memory can lead to better lightly loaded performance private static final boolean _CACHE_VIEW_ROOT_PER_WINDOW = false; public static String getActivePageToken(RequestContext trinContext, ExternalContext external) { String activePageStateKey = _getActivePageTokenKey(external, trinContext); if (activePageStateKey != null) { String tokenPrefix = _getViewCacheKey(external, trinContext, _SUBKEY_SEPARATOR); String tokenSuffix = (String)external.getSessionMap().get(activePageStateKey); return tokenPrefix + tokenSuffix; } else { return null; } } /** * 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; } /** * Returns whether a token is a currently valid View State Token * @param external The ExternalContext * @param token The state token to check for validity * @return */ public static boolean isValidViewStateToken(ExternalContext external, String token) { if ((token != null) && _calculateTokenStateSaving(external, true)) { return (_getPageState(external, token) != null); } else { return false; } } /** * Give a valid state token not associated with a window, change it to be associated with * the specified window Id. * @param external The ExternalContext * @param windowId window id to store the state token under * @param token The state token to remap from windowless to windowed */ public static void remapViewState(ExternalContext external, String windowId, String token) { // remove the view state stored with no associated window from the session String oldSubkey = _getViewCacheKey(external, null, _SUBKEY_SEPARATOR); Map sessionMap = external.getSessionMap(); Map oldStateMap = new SubKeyMap(sessionMap, oldSubkey); PageState viewState = oldStateMap.remove(token); if (viewState == null) throw new IllegalArgumentException(); // store it under the windowId String windowSubkey = _getPerWindowCacheKey(windowId, _VIEW_CACHE_KEY, _SUBKEY_SEPARATOR); Map newStateMap = new SubKeyMap(sessionMap, windowSubkey); newStateMap.put(token, viewState); } /** * Removes all view states per token cache identified by a windowId. When token state saving is used, * a token cache is created for each unique window. The windowId is used to locate the token cache. Each PageState * is located in session scope by a composite key that contains both the window id and state token. When a system window * closed event is generated, this operation should be called to purge stale view state. * * @param extContext faces external context * @param windowId window id to store the state token under */ public static void removeViewStatesForWindow(ExternalContext extContext, String windowId) { // build a key used to locate the token cache for the target windowId // Can't call _getTokenCacheKey() because it doesn't // take a window id, so directly calling _getPerWindowCacheKey String cacheKey = _getPerWindowCacheKey(windowId, _VIEW_CACHE_KEY, null); // use the helper factory method to locate a token cache for the window. the 3rd actual parameter is false // indicating that a new cache should not be created if it doesn't exist. TokenCache cache = TokenCache.getTokenCacheFromSession(extContext, cacheKey, false, -1); if (cache != null) { // create a sub key that is used by the SubKeyMap to locate PageState per token // Can't call _getViewCacheKey() because it doesn't // take a window id, so directly calling _getPerWindowCacheKey String subkey = _getPerWindowCacheKey(windowId, _VIEW_CACHE_KEY, _SUBKEY_SEPARATOR); Map sessionMap = extContext.getSessionMap(); // create a map that establishes a moniker (key) between the windowId and token cache used to locate the // associated page state. Map stateMap = new SubKeyMap(sessionMap, subkey); // removes all page states known by the token cache from session cache.clear(stateMap); // removes the token cache sessionMap.remove(cacheKey); } } /** * Returns the PageState for a state token * @param external * @param token * @return */ private static PageState _getPageState(ExternalContext external, String token) { // get view cache key with "." separator suffix to separate the SubKeyMap keys String subkey = _getViewCacheKey(external, RequestContext.getCurrentInstance(), _SUBKEY_SEPARATOR); Map stateMap = new SubKeyMap( external.getSessionMap(), subkey); return stateMap.get(token); } @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. Map requestMap = extContext.getRequestMap(); UIViewRoot launchView = (UIViewRoot) requestMap.remove(RequestContextImpl.LAUNCH_VIEW); if (launchView != null) { TrinidadPhaseListener.markPostback(context); return launchView; } final Object structure; final Object state; ResponseStateManager rsm = _getResponseStateManager(context, renderKitId); if (_saveAsToken(context, true)) { // we saved the token in the structure portion of the state, so retrieve the // structure portion of the state to get the token. String token = (String)rsm.getTreeStructureToRestore(context, viewId); if (token == null) { _LOG.finest("No token in the request for view \"{0}\"; probably a first view.", viewId); return null; } _LOG.finer("Restoring saved view state for token {0}", token); // get the PageState for the token PageState viewState = _getPageState(extContext, token); if (viewState != null) _updateRequestTokenForResponse(context, token); RequestContext trinContext = RequestContext.getCurrentInstance(); // 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); if(TokenCacheDebugUtils.debugTokenCache()) { TokenCacheDebugUtils.startLog("Restore View"); String sessionId = ""; boolean isNewSession = false; Object session = extContext.getSession(false); if (session == null) { isNewSession = true; } else if (session instanceof HttpSession) { isNewSession = ((HttpSession)session).isNew(); sessionId = ((HttpSession)session).getId(); } if (isNewSession) { TokenCacheDebugUtils.addToLog("The session is new. Session id = " + sessionId); } else { TokenCacheDebugUtils.addToLog("The session is NOT new. Session id = " + sessionId); } // get the state map String subkey = _getViewCacheKey(extContext, RequestContext.getCurrentInstance(), _SUBKEY_SEPARATOR); Map stateMap = new SubKeyMap( extContext.getSessionMap(), subkey); // log what's currently in the state map TokenCacheDebugUtils.logCacheInfo(stateMap, null, "token '" + token + "' not found"); _LOG.severe(TokenCacheDebugUtils.getLogString()); } 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; } StateManagementStrategy sms = _getStateManagementStrategy(context, viewId); if (sms!= null) { // TODO This is a hack because stateManagementStrategy doesn't take // a state object as a param, instead it always asks the responseStateManager // for the state, so push the state onto the request where the CoreResponseStateManager // can return it. We will file a bug agains JSF 2.0 asking that the // stateManagementStrategy deprecate the current restoreView method in favor of // a restoreView method that takes state try { requestMap.put(RESPONSE_STATE_MANAGER_STATE_KEY, viewState.getViewState(context)); root = sms.restoreView(context, viewId, renderKitId); } finally { requestMap.remove(RESPONSE_STATE_MANAGER_STATE_KEY); } return root; } else { Object[] stateArray = (Object[])viewState.getViewState(context); structure = stateArray[0]; state = stateArray[1]; } } else { StateManagementStrategy sms = _getStateManagementStrategy(context, viewId); if (sms!= null) { return sms.restoreView(context, viewId, renderKitId); } Object[] stateArray = (Object[])rsm.getState(context, viewId); structure = stateArray[0]; state = stateArray[1]; } 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); _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().getAttributes(); 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 key that the TokenCache * @param extContext * @return */ private static String _getTokenCacheKey(RequestContext trinContext, ExternalContext extContext) { return _getViewCacheKey(extContext, trinContext, null); } /** * Returns the TokenCache for the current window * @param trinContext * @param extContext * @return */ private TokenCache _getViewCache(RequestContext trinContext, ExternalContext extContext) { String cacheKey = _getTokenCacheKey(trinContext, extContext); 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(_getCurrWindowId(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(_getCurrWindowId(extContext, trinContext), _VIEW_CACHE_KEY, suffix); } /** * Returns the current windowId, if any * @param external * @param trinContext * @return */ static private String _getCurrWindowId(ExternalContext external, RequestContext trinContext) { if (trinContext != null) { WindowManager wm = trinContext.getWindowManager(); if (wm != null) { Window currWindow = wm.getCurrentWindow(external); if (currWindow != null) { return currWindow.getId(); } } } return null; } /** * 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 windowId * @param prefix * @param suffix * @return */ private static String _getPerWindowCacheKey( String windowId, String prefix, Character suffix) { // if we have a current window or a suffix, we need a StringBuilder to calculate the cache key if ((windowId != null) || (suffix != null)) { // compute the extra size neeeded to store the windowId and its separator int windowPartSize; if (windowId != null) { // add 1 for separator windowPartSize = windowId.length() + 1; } else { 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 (windowId != null) { keyBuilder.append('.'); keyBuilder.append(windowId); } // add the suffix if any if (suffix != null) keyBuilder.append(suffix); return keyBuilder.toString(); } else { return prefix; } } /** * Returns true if we should use token state saving rather than client state * saving * @param external * @return * @see #_saveAsToken */ private static boolean _calculateTokenStateSaving(ExternalContext external, boolean checkRequestToken) { Map initParameters = external.getInitParameterMap(); Object stateSavingMethod = initParameters.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 = initParameters.get(CLIENT_STATE_METHOD_PARAM_NAME); if ((clientMethod != null) && CLIENT_STATE_METHOD_ALL.equalsIgnoreCase((String) clientMethod)) { return false; } if (checkRequestToken) { // 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 (viewStateValue != null && !viewStateValue.startsWith("!")) { return false; } } // In certain situations this method is called from a filter and there's no facesContext, so // make sure to check for a null context FacesContext context = FacesContext.getCurrentInstance(); if (context != null) { // 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 = context.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; } /** * 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. * @see #_calculateTokenStateSaving */ private boolean _saveAsToken(FacesContext context, boolean checkRequestToken) { // the setPerViewStateSaving() method stores the PER_PAGE_STATE_SAVING value on the // FacesContext attribute map, during rendering Map attrMap = FacesContext.getCurrentInstance().getAttributes(); 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(); } return _calculateTokenStateSaving(context.getExternalContext(), checkRequestToken); } 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; } private PseudoReferenceFactory _getOrCreateViewRootStateRefFactory( FacesContext context, RequestContext trinContext) { if (_viewRootStateRefFactoryHolder == null) _viewRootStateRefFactoryHolder = _getViewRootStateRefFactoryHolder(context, trinContext); return _viewRootStateRefFactoryHolder.get(); } /** * @return the factory to use for creating references to the UIViewRootState. If * null, no UIViewRoot caching should be performed. */ private static PseudoReferenceFactory _getViewRootStateRefFactory( FacesContext context, RequestContext trinContext) { return _getViewRootStateRefFactoryHolder(context, trinContext).get(); } /** * @return the holder for the factory to use for creating references to the UIViewRootState. If * null, no UIViewRoot caching should be performed. */ private static AtomicReference> _getViewRootStateRefFactoryHolder(FacesContext context, RequestContext trinContext) { ConcurrentMap sharedAppMap = trinContext.getApplicationScopedConcurrentMap(); AtomicReference> factoryHolder = (AtomicReference>)sharedAppMap.get(CACHE_VIEW_ROOT_INIT_PARAM); if (factoryHolder != null) { return factoryHolder; } else { ExternalContext extContext = context.getExternalContext(); String viewRootCaching = extContext.getInitParameter(CACHE_VIEW_ROOT_INIT_PARAM); String caseInsensitiveViewRootCaching; if ((viewRootCaching != null) && (viewRootCaching.length() > 0)) caseInsensitiveViewRootCaching = viewRootCaching.toLowerCase(); else caseInsensitiveViewRootCaching = "true"; // the default PseudoReferenceFactory factory; if ("false".equals(caseInsensitiveViewRootCaching)) { factory = null; } else if ("strong".equals(caseInsensitiveViewRootCaching)) factory = new StrongPseudoReferenceFactory(); else if ("soft".equals(caseInsensitiveViewRootCaching)) factory = new SoftPseudoReferenceFactory(); else if ("true".equals(caseInsensitiveViewRootCaching)) { factory = _instantiateDefaultPseudoReferenceFactory(); } else { factory = _instantiatePseudoReferenceFactoryFromClass(viewRootCaching); if (factory == null) { // we had an error, so use the default factory = _instantiateDefaultPseudoReferenceFactory(); } } // use a placeholder for null, since ConcurrentHashMap can't store null; factoryHolder = new AtomicReference>(factory); sharedAppMap.put(CACHE_VIEW_ROOT_INIT_PARAM, factoryHolder); return factoryHolder; } } /** * Attempt to instantiate a PseudoReferenceFactory from the * CACHE_VIEW_ROOT_INIT_PARAM service, using a StrongPseudoReferenceFactory if no service * is specified or the service is not of the correct type. * null if any errors occur. * @return */ private static PseudoReferenceFactory _instantiateDefaultPseudoReferenceFactory() { PseudoReferenceFactory factory = new StrongPseudoReferenceFactory(); Object service = ClassLoaderUtils.getService(CACHE_VIEW_ROOT_INIT_PARAM); if (service != null) { try { factory = (PseudoReferenceFactory)service; } catch (ClassCastException e) { _LOG.warning("Default service registered for " + CACHE_VIEW_ROOT_INIT_PARAM + " is not of the correct type.", e); } } return factory; } /** * Attempt to instantiate a PseudoReferenceFactory from a class name, returning * null if any errors occur. * @param className * @return */ private static PseudoReferenceFactory _instantiatePseudoReferenceFactoryFromClass( String className) { try { Class> factoryClass = (Class>)ClassLoaderUtils.loadClass(className); return factoryClass.newInstance(); } catch (ClassNotFoundException e) { _LOG.warning("Could not find PseudoReferenceFactory class " + className + " for " + " Servlet Initialization Parameter " + CACHE_VIEW_ROOT_INIT_PARAM); } catch (InstantiationException e) { _logInstantiationError(className, e); } catch (IllegalAccessException e) { _logInstantiationError(className, e); } catch (ClassCastException e) { _logInstantiationError(className, e); } return null; } private static void _logInstantiationError(String className, Throwable e) { _LOG.warning("Could not instantiate a PseudoReferenceFactory for Servlet Initialization" + " Parameter " + CACHE_VIEW_ROOT_INIT_PARAM + " with value " + className, e); } private boolean _needStructure(FacesContext context) { // TODO - partial state saving is handled by facelets in JSF 2.0, // remove this method and anything that depends on it? return true; } 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); } } } private Object _getCachedViewState( FacesContext context) { return context.getExternalContext(). getRequestMap().get(_CACHED_VIEW_STATE); } private void _saveCachedViewState( FacesContext context, Object state) { context.getExternalContext().getRequestMap().put(_CACHED_VIEW_STATE, state); } private StateManagementStrategy _getStateManagementStrategy(FacesContext context, String viewId) { ViewDeclarationLanguage vdl = context.getApplication().getViewHandler(). getViewDeclarationLanguage(context, viewId); if (vdl != null) { return vdl.getStateManagementStrategy(context, viewId); } else { return null; } } 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. We use a PseudReference so that // we can support either soft or strong references private transient PseudoReference _cachedState; public PageState( FacesContext context, PseudoReferenceFactory viewRootStateRefFactory, Object viewState, UIViewRoot root) { if (!(viewState instanceof Serializable)) throw new IllegalArgumentException("Viewstate " + viewState + " is not a Serializable"); boolean zipState = _zipState(context); if (zipState || StateUtils.checkComponentTreeStateSerialization(context)) { if (zipState) { // zip the page state. This will also catch any serialization problems. _viewState = _zipToBytes(viewState); } else { _viewState = viewState; // 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); } } } else { _viewState = viewState; } // we need this state, as we are going to recreate the UIViewRoot later. see // the popRoot() method: _cachedState = ((root != null) && (viewRootStateRefFactory != null)) ? viewRootStateRefFactory.create(new ViewRootState(context, root), null) : null; } public Object getViewState(FacesContext context) { if (_zipState(context)) { return _unzipBytes((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) { ViewRootState rootState = _cachedState.get(); if (rootState != null) { root = rootState.getViewRoot(); viewRootState = rootState.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); // copy facets if (root.getFacetCount() > 0) { Map facets = new HashMap(root.getFacetCount()); facets.putAll(root.getFacets()); newRoot.getFacets().putAll(facets); } 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(byte[] zippedBytes) { Inflater decompressor = new Inflater(); try { 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 { decompressor.end(); } } /** * Returned the zipped version of the viewState */ private byte[] _zipToBytes(Object viewState) { Deflater compresser = new Deflater(Deflater.BEST_SPEED); try { //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); } return baos.toByteArray(); } catch (IOException e) { throw new RuntimeException(_LOG.getMessage("ZIP_STATE_FAILED"), e); } finally { compresser.end(); } } } /** * 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; } // TODO - we used to delegate to the RI when the stateManagement method was server, // but we no longer do that, do we really need _delegate any more? private final StateManager _delegate; private AtomicReference> _viewRootStateRefFactoryHolder; private static final Character _SUBKEY_SEPARATOR = new Character('.'); private static final int _DEFAULT_CACHE_SIZE = 15; // 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"; // 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_VIEW_STATE= "org.apache.myfaces.trinidadinternal.application.CachedViewState"; 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 toekn 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 TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(StateManagerImpl.class); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy