You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by bs...@apache.org on 2010/10/28 02:39:07 UTC
svn commit: r1028148 - in /myfaces/trinidad/branches/1.2.12.3-branch:
trinidad-api/src/main/java/org/apache/myfaces/trinidad/bean/util/
trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/
trinidad-impl/src/main/java/org/apache/myfaces/trinidad...
Author: bsullivan
Date: Thu Oct 28 00:39:07 2010
New Revision: 1028148
URL: http://svn.apache.org/viewvc?rev=1028148&view=rev
Log:
[Trinidad-1858] Fix fail-over bugs where session state is mutated without dirtying the Session objects
Backport to 1.2.12.3
Modified:
myfaces/trinidad/branches/1.2.12.3-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/bean/util/StateUtils.java
myfaces/trinidad/branches/1.2.12.3-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/CollectionUtils.java
myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/application/StateManagerImpl.java
myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/CheckSerializationConfigurator.java
myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/GlobalConfiguratorImpl.java
myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/context/PageFlowScopeMap.java
myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/SubKeyMap.java
myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java
myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/webapp/TrinidadFilterImpl.java
myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/xrts/org/apache/myfaces/trinidadinternal/resource/LoggerBundle.xrts
Modified: myfaces/trinidad/branches/1.2.12.3-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/bean/util/StateUtils.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.12.3-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/bean/util/StateUtils.java?rev=1028148&r1=1028147&r2=1028148&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.12.3-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/bean/util/StateUtils.java (original)
+++ myfaces/trinidad/branches/1.2.12.3-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/bean/util/StateUtils.java Thu Oct 28 00:39:07 2010
@@ -57,6 +57,7 @@ public final class StateUtils
boolean checkComponentTreeStateSerialization = false;
boolean checkSessionSerialization = false;
boolean checkApplicationSerialization = false;
+ boolean checkMangedBeanMutation = false;
String checkSerializationProperty;
@@ -90,6 +91,7 @@ public final class StateUtils
checkComponentTreeStateSerialization = true;
checkSessionSerialization = true;
checkApplicationSerialization = true;
+ checkMangedBeanMutation = true;
}
else
{
@@ -98,6 +100,7 @@ public final class StateUtils
checkComponentTreeStateSerialization = serializationFlags.contains("TREE");
checkSessionSerialization = serializationFlags.contains("SESSION");
checkApplicationSerialization = serializationFlags.contains("APPLICATION");
+ checkMangedBeanMutation = serializationFlags.contains("BEANS");
}
}
}
@@ -107,6 +110,7 @@ public final class StateUtils
_CHECK_COMPONENT_TREE_STATE_SERIALIZATION = checkComponentTreeStateSerialization;
_CHECK_SESSION_SERIALIZATION = checkSessionSerialization;
_CHECK_APPLICATION_SERIALIZATION = checkApplicationSerialization;
+ _CHECK_MANAGED_BEAN_MUTATATION = checkMangedBeanMutation;
}
private static final boolean _CHECK_COMPONENT_TREE_STATE_SERIALIZATION;
@@ -114,6 +118,7 @@ public final class StateUtils
private static final boolean _CHECK_PROPERTY_STATE_SERIALIZATION;
private static final boolean _CHECK_SESSION_SERIALIZATION;
private static final boolean _CHECK_APPLICATION_SERIALIZATION;
+ private static final boolean _CHECK_MANAGED_BEAN_MUTATATION;
/**
* Returns <code>true</code> if properties should be checked for
@@ -200,6 +205,7 @@ public final class StateUtils
* @see #checkComponentStateSerialization
* @see #checkComponentTreeStateSerialization
* @see #checkApplicationSerialization
+ * @see #checkManagedBeanMutation
*/
public static boolean checkSessionSerialization(ExternalContext extContext)
{
@@ -222,6 +228,7 @@ public final class StateUtils
* @see #checkComponentStateSerialization
* @see #checkComponentTreeStateSerialization
* @see #checkSessionSerialization
+ * @see #checkManagedBeanMutation
*/
public static boolean checkApplicationSerialization(ExternalContext extContext)
{
@@ -229,6 +236,22 @@ public final class StateUtils
}
/**
+ * Returns <code>true</code> if the attributes of the session and application Maps should be
+ * checked for cases where the attribute was mutated but not dirtied for failover. If
+ * <code>checkSessionSerialization</code> returns <code>true</code>, the contents of the
+ * Session should be checked. If <code>checkApplicationSerialization</code> returns
+ * <code>true</code>, the Serializable content of the Application should be checked.
+ * @return true if the contents of scopes should be checked for mutation without dirtying.
+ * @see #checkApplicationSerialization
+ * @see #checkSessionSerialization
+ */
+ public static boolean checkManagedBeanMutation(ExternalContext extContext)
+ {
+ return _CHECK_MANAGED_BEAN_MUTATATION;
+ }
+
+
+ /**
* Persists a property key.
*/
static public Object saveKey(PropertyKey key)
@@ -591,6 +614,3 @@ public final class StateUtils
}
-
-
-
Modified: myfaces/trinidad/branches/1.2.12.3-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/CollectionUtils.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.12.3-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/CollectionUtils.java?rev=1028148&r1=1028147&r2=1028148&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.12.3-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/CollectionUtils.java (original)
+++ myfaces/trinidad/branches/1.2.12.3-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/CollectionUtils.java Thu Oct 28 00:39:07 2010
@@ -24,11 +24,8 @@ import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Array;
-
import java.util.AbstractQueue;
-import java.util.AbstractSet;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
@@ -43,11 +40,9 @@ import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.RandomAccess;
import java.util.Set;
-
import java.util.concurrent.atomic.AtomicReference;
import org.apache.myfaces.trinidad.component.CompositeIterator;
-import org.apache.myfaces.trinidad.context.Version;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
/**
@@ -1725,7 +1720,7 @@ public final class CollectionUtils
e);
}
}
- else
+ else if (value != null)
{
if (_requireSerializable)
{
Modified: myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/application/StateManagerImpl.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/application/StateManagerImpl.java?rev=1028148&r1=1028147&r2=1028148&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/application/StateManagerImpl.java (original)
+++ myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/application/StateManagerImpl.java Thu Oct 28 00:39:07 2010
@@ -326,11 +326,13 @@ public class StateManagerImpl extends St
if (applicationViewCache == null)
{
assert(!dontSave);
- TokenCache cache = _getViewCache(context);
+
+ RequestContext trinContext = RequestContext.getCurrentInstance();
+
+ TokenCache cache = _getViewCache(trinContext, extContext);
assert(cache != null);
Map<String, Object> sessionMap = extContext.getSessionMap();
- RequestContext trinContext = RequestContext.getCurrentInstance();
// get view cache key with "." separator suffix to separate the SubKeyMap keys
String subkey = _getViewCacheKey(extContext, trinContext, _SUBKEY_SEPARATOR);
@@ -599,10 +601,10 @@ public class StateManagerImpl extends St
}
else
{
+ RequestContext trinContext = RequestContext.getCurrentInstance();
+
// get view cache key with "." separator suffix to separate the SubKeyMap keys
- String subkey = _getViewCacheKey(extContext,
- RequestContext.getCurrentInstance(),
- _SUBKEY_SEPARATOR);
+ String subkey = _getViewCacheKey(extContext, trinContext, _SUBKEY_SEPARATOR);
Map<String, PageState> stateMap = new SubKeyMap<PageState>(
extContext.getSessionMap(),
@@ -619,8 +621,7 @@ public class StateManagerImpl extends St
// 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(context).isAvailable((String) token);
+ boolean isAvailable = _getViewCache(trinContext, extContext).isAvailable((String) token);
assert ((viewState != null) == isAvailable);
}
@@ -763,21 +764,19 @@ public class StateManagerImpl extends St
throw new UnsupportedOperationException();
}
-
- private TokenCache _getViewCache(FacesContext context)
+ /**
+ * Returns the TokenCache for the current window
+ * @param trinContext
+ * @param extContext
+ * @return
+ */
+ private TokenCache _getViewCache(RequestContext trinContext, ExternalContext extContext)
{
- ExternalContext extContext = context.getExternalContext();
-
- return TokenCache.getTokenCacheFromSession(context,
- _getViewCacheKey(extContext,
- RequestContext.getCurrentInstance(),
- null),
- true,
- _getCacheSize(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
Modified: myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/CheckSerializationConfigurator.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/CheckSerializationConfigurator.java?rev=1028148&r1=1028147&r2=1028148&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/CheckSerializationConfigurator.java (original)
+++ myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/CheckSerializationConfigurator.java Thu Oct 28 00:39:07 2010
@@ -18,18 +18,57 @@
*/
package org.apache.myfaces.trinidadinternal.config;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
import javax.faces.context.ExternalContext;
+import org.apache.myfaces.trinidad.context.ExternalContextDecorator;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+
+import javax.servlet.http.HttpSessionContext;
+
import org.apache.myfaces.trinidad.bean.util.StateUtils;
import org.apache.myfaces.trinidad.config.Configurator;
-import org.apache.myfaces.trinidad.context.ExternalContextDecorator;
+import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.util.CollectionUtils;
+import org.apache.myfaces.trinidad.util.CollectionUtils.MapMutationHooks;
+import org.apache.myfaces.trinidad.util.TransientHolder;
+
+import org.apache.myfaces.trinidadinternal.context.external.ServletApplicationMap;
/**
- * Configurator that uses a wrapped ExternalContext to return a Session Map that validates that
- * only Serializable Objects are placed in the Map.
+ * Configurator that uses both wrapped ExternalContext (for Portlet cases) and wrapped
+ * ServletContext and HttpSession (for HttpServlet cases) to validate that
+ * only Serializable Objects are placed in the Sesssion Map and that mutations to the
+ * Session and ApplicationMap content dirty the entries.
* @version $Revision$ $Date$
*/
public final class CheckSerializationConfigurator extends Configurator
@@ -37,89 +76,1171 @@ public final class CheckSerializationCon
/**
* Override to return our ExternalContext wrapped if session serialization checking is enabled
- * @param externalContext
+ * @param extContext
* @return
*/
@Override
- public ExternalContext getExternalContext(ExternalContext externalContext)
+ public ExternalContext getExternalContext(ExternalContext extContext)
{
- boolean checkSession = StateUtils.checkSessionSerialization(externalContext);
- boolean checkApplication = StateUtils.checkApplicationSerialization(externalContext);
-
- if (checkSession || checkApplication)
+ // retrieve our wrapped ExternalContext, creating it if necessary. We wrap the external
+ // context to trap calls to retrieve the Session and Application Maps in the portal case.
+ // For the HttpServlet case, we rely on
+ ExternalContext checkingContext =
+ SerializationCheckingWrapper.getSerializationWrapper(extContext, true);
+
+ if (checkingContext != null)
+ {
+ return checkingContext;
+ }
+ else
+ {
+ return extContext;
+ }
+ }
+
+ /**
+ * Check if any of the non-dirtied checked managed beans have been mutated in this request
+ * @param extContext
+ */
+ @Override
+ public void endRequest(ExternalContext extContext)
+ {
+ // get the wrapper without creating it if it doesn't already exist
+ SerializationCheckingWrapper checkingWrapper =
+ SerializationCheckingWrapper.getSerializationWrapper(extContext, false);
+
+
+ if (checkingWrapper != null)
+ {
+ checkingWrapper.checkForMutations();
+ }
+ }
+
+ /**
+ * Returns the FilterConfig to use for initializing the filters so that we can wrap it
+ * if necessary
+ * @param filterConfig
+ * @return
+ */
+ public static FilterConfig getFilterConfig(FilterConfig filterConfig)
+ {
+ // skankily don't pass the ExternalContext since we don't have one when the filters are
+ // initialized. This only really works because checkApplicvationSerialization doesn't
+ // need the ExternalContext
+ if (StateUtils.checkApplicationSerialization(null))
+ {
+ return new FilterConfigWrapper(filterConfig);
+ }
+ else
{
- return new SessionSerializationChecker(externalContext, checkSession, checkApplication);
+ return filterConfig;
+ }
+ }
+
+ /**
+ * Returns the HttpServletRequest to use for this request, so that we can wrap it if
+ * necessary.
+ * @param extContext
+ * @param request
+ * @return
+ */
+ public static HttpServletRequest getHttpServletRequest(
+ ExternalContext extContext,
+ HttpServletRequest request)
+ {
+ SerializationChecker checker = SerializationChecker.getSerializationChecker(extContext, true);
+
+ if (checker != null)
+ {
+ return checker.getWrappedRequest(request);
}
else
{
- return externalContext;
+ return request;
}
}
+
+ /**
+ * Unregisters the checking of the specified session attribute
+ * @param external ExternalContext
+ * @param key Name of session attribute to unregister
+ */
+ public static void unregisterSessionAttribute(ExternalContext external, String key)
+ {
+ SerializationChecker checker = SerializationChecker.getSerializationChecker(external, false);
+
+ if (checker != null)
+ {
+ checker.unregisterSessionAttribute(external, key);
+ }
+ }
+
+ /**
+ * Unregisters the checking of the specified application attribute
+ * @param external ExternalContext
+ * @param key Name of session attribute to unregister
+ */
+ public static void unregisterApplicationAttribute(ExternalContext external, String key)
+ {
+ SerializationChecker checker = SerializationChecker.getSerializationChecker(external, false);
+
+ if (checker != null)
+ {
+ checker.unregisterApplicationAttribute(external, key);
+ }
+ }
+
+ /**
+ * Returns the list of in-flight MutatedBeanCheckers for the specified Map and its associated
+ * lock object
+ * @param checkedMap Map checked by the MutatedBeanCheckers
+ * @param mapWriteLock Lock object to synchronize on when mutatating the amp
+ * @return
+ */
+ private static List<MutatedBeanChecker> _getMutatedBeanList(
+ Map<String, Object> checkedMap,
+ Object mapWriteLock)
+ {
+ Object list = checkedMap.get(_CHECKED_MAPS_KEY);
+
+ if (list == null)
+ {
+ // make sure that the list is only created once per map
+ synchronized(mapWriteLock)
+ {
+ // check again in case we value was written by the previou lock holder
+ list = checkedMap.get(_CHECKED_MAPS_KEY);
+
+ if (list == null)
+ {
+ // mutations to the list itself need to be thread-safe
+ List<MutatedBeanChecker> beanList = new CopyOnWriteArrayList<MutatedBeanChecker>();
+
+ // use a TransientHolder since we don't care if this gets failed over
+ checkedMap.put(_CHECKED_MAPS_KEY, TransientHolder.newTransientHolder(beanList));
+
+ return beanList;
+ }
+ }
+ }
+
+ return ((TransientHolder<List<MutatedBeanChecker>>)list).getValue();
+ }
+
+ /**
+ * Remove the modified key from all of the in-flight bean checking requests
+ * @param beanCheckers
+ * @param key
+ */
+ private static void _notifyBeanCheckersOfChange(
+ List<MutatedBeanChecker> beanCheckers,
+ Object key)
+ {
+ for (MutatedBeanChecker beanChecker : beanCheckers)
+ {
+ beanChecker._unmutatedKeyValues.remove(key);
+ }
+ }
+
+ /**
+ * Wraps the FilterConfig so that we can wrap the ServletContext that it returns so that
+ * we can trap calls the setting and removing ServletContext attributes. Phew!
+ */
+ private static class FilterConfigWrapper implements FilterConfig
+ {
+ FilterConfigWrapper(FilterConfig filterConfig)
+ {
+ _delegate = filterConfig;
+
+ // create ServletContext wrapper to catch sets and removes from the ServletContext
+ _wrappedContext = new ContextWrapper(filterConfig.getServletContext(), null);
+ }
+
+ public String getFilterName()
+ {
+ return _delegate.getFilterName();
+ }
+
+ public ServletContext getServletContext()
+ {
+ return _wrappedContext;
+ }
+
+ public String getInitParameter(String paramName)
+ {
+ return _delegate.getInitParameter(paramName);
+ }
+
+ public Enumeration getInitParameterNames()
+ {
+ return _delegate.getInitParameterNames();
+ }
+
+ private final FilterConfig _delegate;
+ private final ServletContext _wrappedContext;
+ }
+
+
/**
- * ExternalContextDecorator returning our wrapped SessionMap
+ * ExternalContextWrapper that returns wrapped versions of the Session and ApplicationMaps that
+ * we can track changes to. This is needed for the Portlet case. For the HttpServel case,
+ * it is redundant with the ServletContext and Session wrapping but shouldn't do any harm.
*/
- private static class SessionSerializationChecker extends ExternalContextDecorator
+ private static class SerializationCheckingWrapper extends ExternalContextDecorator
{
- public SessionSerializationChecker(
+ /**
+ * Retrieves the current SerializationCheckingWrapper for this request
+ * @param extContext
+ * @param create If <code>true</code>, create the SerializationCheckingWrapper for this
+ * request if it doesn't already exist.
+ * @return
+ */
+ public static SerializationCheckingWrapper getSerializationWrapper(
ExternalContext extContext,
- boolean checkSession,
- boolean checkApplication)
+ boolean create)
+ {
+ // get the SerializationCheckingWrapper for this request
+ Map<String, Object> requestMap = extContext.getRequestMap();
+
+ Object wrapper = requestMap.get(_SERIALIZATION_WRAPPER_KEY);
+
+ if (wrapper != null)
+ {
+ return (SerializationCheckingWrapper)wrapper;
+ }
+ else if (create)
+ {
+ // create the wrapper for this request and store it on the request so that we don't
+ // recreate it
+ SerializationChecker checker = SerializationChecker.getSerializationChecker(extContext,
+ create);
+
+ if (checker != null)
+ {
+ SerializationCheckingWrapper checkingWrapper = new SerializationCheckingWrapper(extContext,
+ checker);
+
+ requestMap.put(_SERIALIZATION_WRAPPER_KEY, checkingWrapper);
+
+ return checkingWrapper;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Create a SerializationCheckingWrapper
+ * @param extContext ExternalContext to wrap
+ * @param checker SerializationChecker to call back on mutations
+ */
+ private SerializationCheckingWrapper(
+ ExternalContext extContext,
+ SerializationChecker checker)
{
_extContext = extContext;
+ _checker = checker;
+ }
+
+ /**
+ * Check for mutations to beans trancked by the SerializationCheckingWrapper
+ */
+ public void checkForMutations()
+ {
+ _checker.checkForMutations();
+ }
+
+ @Override
+ public ExternalContext getExternalContext()
+ {
+ return _extContext;
+ }
+
+ /**
+ * Override to delegate to the SerializationChecker
+ * @return
+ */
+ @Override
+ public Map<String, Object> getSessionMap()
+ {
+ return _checker.getSessionMap();
+ }
+
+ /**
+ * Override to delegate to the SerializationChecker
+ * @return
+ */
+ @Override
+ public Map<String, Object> getApplicationMap()
+ {
+ return _checker.getApplicationMap();
+ }
+
+ private static final String _SERIALIZATION_WRAPPER_KEY =
+ CheckSerializationConfigurator.class.getName() + "#WRAPPER";
+
+ private final ExternalContext _extContext;
+ private final SerializationChecker _checker;
+ }
+
+ /**
+ * Checks a Map for mutations to the contents of its Serializable attributes that weren't
+ * dirtied in the current request.
+ * The mutations are checked by snapshotting the serialized bytes of the current Serializable
+ * values of the Map at the beginning of the request. We then catch all puts and removes to
+ * the Map across any request and remove those entries, since they have been correctly dirtied.
+ * At the end of the request. <code>checkForMutations</code> is called and we iterate over
+ * the remaining entries and compared their serialized bytes against the serialized bytes
+ * of the current values. If the bytes are different, we assume that some part of the
+ * attribute's object subtree has been changed without appropriately dirtying it and we
+ * log an errror.
+ * @see #checkForMutations
+ */
+ private static class MutatedBeanChecker implements MapMutationHooks<String, Object>
+ {
+ /**
+ * Creates a MutatedBeanChecker to check for undirtied mutations to the Serialized state
+ * of a Map in the current request.
+ * @param checkedMap Map containing attributes to check for mutations
+ * @param mapName Name of map used when logging
+ * @param mapLock Lock to use when mutating the map
+ * @param requireSerialization <code>true</code> if all of the attributes are required to be
+ * Serializable (Sesssion Map attributes are. Application Map
+ * aren't)
+ */
+ public MutatedBeanChecker(
+ Map<String, Object> checkedMap,
+ String mapName,
+ Object mapLock,
+ boolean requireSerialization)
+ {
+ _checkedMap = checkedMap;
+ _mapName = mapName;
+ _mapLock = mapLock;
+
+ // snapshot the initial serialized bytes of the mutable values in the Map so that we
+ // can compare them at the end of the request to see if they have changed
+ _unmutatedKeyValues = new ConcurrentHashMap<String, Object>(checkedMap.size() * 2);
+ _unmutatedKeyValues.putAll(checkedMap);
+
+ // register this request with the list of checkers for this map.
+ // I think there is a possible race condition between the keys being added to the map and
+ // our registering ourselves as listening for puts and removes made by other requests.
+ // This makes me sad
+ _getMutatedBeanList().add(this);
+
+ // loop through the map's valid keys getting the serialized bytes
+ for (String key : _unmutatedKeyValues.keySet())
+ {
+ Object value = checkedMap.get(key);
+
+ // get the serialized bytes for this key. If the key's vale isn't serializable or is
+ // immutable, this will be an empty array and we won't need to check it
+ byte[] serializedBytes = _getSerializedValue(key, value, requireSerialization);
+
+ if (serializedBytes.length > 0)
+ {
+ // save the bytes for comparing at the end of the request
+ _unmutatedKeyValues.put(key, serializedBytes);
+ }
+ else
+ {
+ // either not serializable or immutable, so we don't need to worry about checking it
+ _unmutatedKeyValues.remove(key);
+ }
+ }
+ }
+
+ /**
+ * Unregisters the checking of the specified attribute
+ * @param key Name of session attribute to unregister
+ */
+ public void unregisterAttribute(String key)
+ {
+ CheckSerializationConfigurator._notifyBeanCheckersOfChange(_getMutatedBeanList(), key);
+ }
+
+ /** Implement to catch writes to the Map, since that means that the Map has been dirtied */
+ @Override
+ public void writeNotify(Map<String, Object> map, String key, Object value)
+ {
+ unregisterAttribute(key);
+ }
+
+ /** Implement to catch remove from the Map, since that means the attribute won't
+ * be failed over
+ */
+ @Override
+ public void removeNotify(Map<String, Object> map, Object key)
+ {
+ unregisterAttribute((String)key);
+ }
+
+ /** Implement to catch clear of Map, since that means the attributes won't
+ * be failed over
+ */
+ @Override
+ public void clearNotify(Map<String, Object> map)
+ {
+ // clear all of the keys across all of the beans
+ for (String key : map.keySet())
+ {
+ unregisterAttribute(key);
+ }
+ }
+
+ /**
+ * Clear all of the values we were checking. This is called if the Session is invalidated,
+ * for example.
+ */
+ public void clearCheckedValues()
+ {
+ for (MutatedBeanChecker beanChecker : _getMutatedBeanList())
+ {
+ beanChecker._unmutatedKeyValues.clear();
+ }
+ }
+
+ /**
+ * Check for any undirtied mutations of this map, logging severe messages if there are
+ */
+ public void checkForMutations()
+ {
+ // loop through the unmodified items in the Map and verify that the curent
+ // Serialized values haven't changed
+ for (Map.Entry<String, Object> checkedEntry : _unmutatedKeyValues.entrySet())
+ {
+ String key = checkedEntry.getKey();
+
+ Object currValue = _checkedMap.get(key);
+ byte[] currentBytes = _getSerializedValue(key, currValue, false);
+ byte[] oldBytes = (byte[])checkedEntry.getValue();
+
+ // check if the bytes are different
+ if (!Arrays.equals(oldBytes, currentBytes))
+ {
+ // deserialize the original object so we can dump it out (hopefully it has a
+ // good toString()) We also do this so we can
+ Object oldValue = _deserializeObject(oldBytes);
+
+ // deserialize the new bytes so that we are comparing two deserialized objects
+ Object newValue = _deserializeObject(currentBytes);
+
+ // This doesn't do anything, but is a handy comparison of debugging when things go awry
+ oldValue.equals(newValue);
+
+ // build up the message
+ String message = _LOG.getMessage("SERIALIZABLE_ATTRIBUTE_MUTATED",
+ new Object[]{_mapName, key, oldValue, newValue});
+
+ // Log message because user might not notice exception since we are at the end of the
+ // request
+ _LOG.severe(message);
+ }
+ }
+
+ // we no longer need to track changes to the map
+ _getMutatedBeanList().remove(this);
+ }
+
+
+ /**
+ * Given the serialized bytes of an Object, return the Object itself
+ * @param serializedBytes
+ * @return
+ */
+ private Object _deserializeObject(byte[] serializedBytes)
+ {
+ Object deserializedObject;
+
+ try
+ {
+ // copy the bytes before passing to the ByteArrayInputStream, since it mutates
+ // the array
+ byte[] copyBytes = Arrays.copyOf(serializedBytes, serializedBytes.length);
+
+ ByteArrayInputStream baos = new ByteArrayInputStream(copyBytes);
+ ObjectInputStream ois = new ObjectInputStream(baos);
+ deserializedObject = ois.readObject();
+ ois.close();
+ }
+ catch (IOException e)
+ {
+ throw new IllegalArgumentException(e);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new IllegalArgumentException(e);
+ }
+
+ return deserializedObject;
+ }
+
+ /**
+ * Returns the List of MutatedBeanCheckers across all in flight requests listening for changes
+ * to this Map
+ * @return
+ */
+ private List<MutatedBeanChecker> _getMutatedBeanList()
+ {
+ return CheckSerializationConfigurator._getMutatedBeanList(_checkedMap, _mapLock);
+ }
+
+ /**
+ * Returns the serialized value of the object as a byte[] or an empty array if the object is
+ * immutable and therefore doesn't need to be checked
+ * @param key Key in map to get the serialized value of
+ * @param value Value in map to serialize
+ * @param requireSerialization <code>true</code> if this value is required to be serializable
+ * @return The serialized bytes for the Object if serializable and mutable
+ * @throws IllegalStateException if the Object is not serializable or serialization fails
+ */
+ private byte[] _getSerializedValue(String key, Object value, boolean requireSerialization)
+ {
+ if (value == null)
+ return _EMPTY_BYTE_ARRAY;
+
+ Class valueClass = value.getClass();
+
+ // check against the lsit of classes to ignore for performance reasons
+ if (_INGNORE_CLASS_NAMES.contains(valueClass.getName()))
+ return _EMPTY_BYTE_ARRAY;
+
+ if (!(value instanceof Serializable))
+ {
+ if (requireSerialization)
+ {
+ String message = _LOG.getMessage("ATTRIBUTE_NOT_SERIALIZABLE",
+ new Object[]{_mapName, key, valueClass});
+
+ // throw new IllegalStateException(message);
+ }
+
+ return _EMPTY_BYTE_ARRAY;
+ }
- Map<String, Object> sessionMap = extContext.getSessionMap();
- if (checkSession)
+ // verify that the contents of the value are in fact Serializable
+ try
{
- // skank using type erasure to finess the fact that Collections.checkedMap() expects and
- // will return a Map<String, Serializable> when in fact, we should be returning a
- // Map<String, Onject>. Using checkedMap also has the disadvantage that a ClassCastException
- // is thrown when we would really prefer to throw a more-explanatory message
- //Map erasedMap = sessionMap;
- //_sessionMap = (Map)Collections.checkedMap(erasedMap, String.class, Serializable.class);
- _sessionMap = CollectionUtils.getCheckedSerializationMap(sessionMap);
+ ByteArrayOutputStream outputByteStream = new ByteArrayOutputStream();
+
+ new ObjectOutputStream(outputByteStream).writeObject(value);
+
+ return outputByteStream.toByteArray();
+ }
+ catch (IOException e)
+ {
+ if (requireSerialization)
+ {
+ String message = _LOG.getMessage("ATTRIBUTE_SERIALIZATION_FAILED",
+ new Object[]{_mapName, key, value});
+
+ throw new IllegalArgumentException(message, e);
+ }
+
+ return _EMPTY_BYTE_ARRAY;
+ }
+ }
+
+ private static final byte[] _EMPTY_BYTE_ARRAY = new byte[0];
+
+ private static final Set<String> _INGNORE_CLASS_NAMES;
+
+ static
+ {
+ // initialize Set of class names to ignore for Serialization tracking because they
+ // are immutable or not serialiable
+ String[] classNames = new String[]
+ {
+ "java.lang.Boolean", // immutable
+ "java.lang.Character", // immutable
+ "java.lang.Double", // immutable
+ "java.lang.Float", // immutable
+ "java.lang.Integer", // immutable
+ "java.lang.Long", // immutable
+ "java.lang.Short", // immutable
+ "java.lang.String", // immutable
+ "java.math.BigDecimal", // immutable
+ "java.math.BigInteger", // immutable
+ "org.apache.myfaces.trinidad.util.TransientHolder" // Not serializable
+ };
+
+
+ _INGNORE_CLASS_NAMES = new HashSet<String>();
+ _INGNORE_CLASS_NAMES.addAll(Arrays.asList(classNames));
+ }
+
+ private static final TrinidadLogger _LOG =
+ TrinidadLogger.createTrinidadLogger(MutatedBeanChecker.class);
+
+ private final Map<String, Object> _unmutatedKeyValues;
+ private final Map<String, Object> _checkedMap;
+ private final String _mapName;
+ private final Object _mapLock;
+ }
+
+ /**
+ * Wraps the ServletContext so that we can catch modifications to the attributes
+ */
+ private static final class ContextWrapper implements ServletContext
+ {
+ ContextWrapper(
+ ServletContext servletContext,
+ Map<String, Object> applicationMap)
+ {
+ _delegate = servletContext;
+
+ // if we already have an Application Map, use it, otherwise create a wrapper around
+ // the ServletContext
+ if (applicationMap != null)
+ {
+ _applicationMap = applicationMap;
}
else
{
- _sessionMap = sessionMap;
+ _applicationMap = new ServletApplicationMap(servletContext);
}
-
+ }
+
+ public String getContextPath()
+ {
+ return _delegate.getContextPath();
+ }
+
+ public ServletContext getContext(String string)
+ {
+ return _delegate.getContext(string);
+ }
+
+ public int getMajorVersion()
+ {
+ return _delegate.getMajorVersion();
+ }
+
+ public int getMinorVersion()
+ {
+ return _delegate.getMinorVersion();
+ }
+
+ public String getMimeType(String string)
+ {
+ return _delegate.getMimeType(string);
+ }
+
+ public Set getResourcePaths(String string)
+ {
+ return _delegate.getResourcePaths(string);
+ }
+
+ public URL getResource(String string) throws MalformedURLException
+ {
+ return _delegate.getResource(string);
+ }
+
+ public InputStream getResourceAsStream(String string)
+ {
+ return _delegate.getResourceAsStream(string);
+ }
+
+ public RequestDispatcher getRequestDispatcher(String string)
+ {
+ return _delegate.getRequestDispatcher(string);
+ }
+
+ public RequestDispatcher getNamedDispatcher(String string)
+ {
+ return _delegate.getNamedDispatcher(string);
+ }
+
+ public Servlet getServlet(String string) throws ServletException
+ {
+ return _delegate.getServlet(string);
+ }
+
+ public Enumeration getServlets()
+ {
+ return _delegate.getServlets();
+ }
+
+ public Enumeration getServletNames()
+ {
+ return _delegate.getServletNames();
+ }
+
+ public void log(String string)
+ {
+ _delegate.log(string);
+ }
+
+ public void log(Exception exception, String string)
+ {
+ _delegate.log(exception, string);
+ }
+
+ public void log(String string, Throwable throwable)
+ {
+ _delegate.log(string, throwable);
+ }
+
+ public String getRealPath(String string)
+ {
+ return _delegate.getRealPath(string);
+ }
+
+ public String getServerInfo()
+ {
+ return _delegate.getServerInfo();
+ }
+
+ public String getInitParameter(String string)
+ {
+ return _delegate.getInitParameter(string);
+ }
+
+ public Enumeration getInitParameterNames()
+ {
+ return _delegate.getInitParameterNames();
+ }
+
+ public Object getAttribute(String key)
+ {
+ return _delegate.getAttribute(key);
+ }
+
+ public Enumeration getAttributeNames()
+ {
+ return _delegate.getAttributeNames();
+ }
+
+ /**
+ * Override to remove the attribute from the list of attributes to fail over
+ * @param key
+ * @param value
+ */
+ public void setAttribute(String key, Object value)
+ {
+ _delegate.setAttribute(key, value);
+
+ _notifyBeanCheckersOfChange(_getMutatedBeanList(), key);
+ }
+
+ /**
+ * Override to remove the attribute from the list of attributes to fail over
+ * @param key
+ */
+ public void removeAttribute(String key)
+ {
+ _delegate.removeAttribute(key);
+
+ _notifyBeanCheckersOfChange(_getMutatedBeanList(), key);
+ }
+
+ public String getServletContextName()
+ {
+ return _delegate.getServletContextName();
+ }
+
+ private List<MutatedBeanChecker> _getMutatedBeanList()
+ {
+ return CheckSerializationConfigurator._getMutatedBeanList(_applicationMap, _delegate);
+ }
+
+ private final ServletContext _delegate;
+ private final Map<String, Object> _applicationMap;
+ }
+
+ /**
+ * Performs any configured serialization checking of the Session or Application Maps including
+ * whether the contents are Serializable and whether the contents have changed.
+ */
+ private static class SerializationChecker
+ {
+ /**
+ * Get the current SerializaionChecker for this request, potentially creating one
+ * @param extContext ExternalContext to use to create the SerializationChecker
+ * @param create If <code>true</code> a SerializationChecker will be created and registered
+ * for this request if one does not alreadfy exist.
+ * @return
+ */
+ public static SerializationChecker getSerializationChecker(
+ ExternalContext extContext,
+ boolean create)
+ {
+ Map<String, Object> requestMap = extContext.getRequestMap();
+
+ Object checker = requestMap.get(_SERIALIZATION_CHECKER_KEY);
+
+ if (checker != null)
+ {
+ return (SerializationChecker)checker;
+ }
+ else if (create)
+ {
+ boolean checkSession = StateUtils.checkSessionSerialization(extContext);
+ boolean checkApplication = StateUtils.checkApplicationSerialization(extContext);
+ boolean checkManagedBeanMutation = StateUtils.checkManagedBeanMutation(extContext);
+
+ // check the possible conditions under which we would need to create a SerializationChecker
+ if (checkSession || checkApplication || checkManagedBeanMutation)
+ {
+ SerializationChecker serializationChecker = new SerializationChecker(
+ extContext,
+ checkSession,
+ checkApplication,
+ checkManagedBeanMutation);
+ requestMap.put(_SERIALIZATION_CHECKER_KEY, serializationChecker);
+
+ return serializationChecker;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates a SerializationChecker for this request
+ * @param extContext ExternalContext to use to initialize the SerializationChecker
+ * @param checkSession If true check serializability of session attributes
+ * @param checkApplication if true, check serializability of application attributes
+ * @param checkManagedBeanMutation if true, check for mutations to attributes in the session
+ * if checkSession is true and the application if
+ * checkApplication is true.
+ */
+ private SerializationChecker(
+ ExternalContext extContext,
+ boolean checkSession,
+ boolean checkApplication,
+ boolean checkManagedBeanMutation)
+ {
+ Map<String, Object> sessionMap = extContext.getSessionMap();
Map<String, Object> applicationMap = extContext.getApplicationMap();
- if (checkApplication)
+ if (checkManagedBeanMutation)
{
- // skank using type erasure to finess the fact that Collections.checkedMap() expects and
- // will return a Map<String, Serializable> when in fact, we should be returning a
- // Map<String, Onject>. Using checkedMap also has the disadvantage that a ClassCastException
- // is thrown when we would really prefer to throw a more-explanatory message
- //Map erasedMap = applicationMap;
- //_applicationMap = (Map)Collections.checkedMap(erasedMap, String.class, Serializable.class);
- _applicationMap = CollectionUtils.getCheckedSerializationMap(applicationMap);
+ // note that the mutated bean checekd implicitly checks for attribute serialization as well.
+ _sessionBeanChecker = new MutatedBeanChecker(sessionMap,
+ "Session",
+ extContext.getSession(true),
+ true);
+ sessionMap = CollectionUtils.newMutationHookedMap(sessionMap, _sessionBeanChecker);
+
+ // only check the application for mutations if the application checking is enabled
+ if (checkApplication)
+ {
+ _applicationBeanChecker = new MutatedBeanChecker(applicationMap,
+ "Application",
+ extContext.getContext(),
+ false);
+ applicationMap = CollectionUtils.newMutationHookedMap(applicationMap,
+ _applicationBeanChecker);
+ }
+ else
+ {
+ _applicationBeanChecker = null;
+ }
}
else
{
- _applicationMap = applicationMap;
+ _sessionBeanChecker = null;
+ _applicationBeanChecker = null;
+
+ if (checkSession)
+ {
+ sessionMap = CollectionUtils.getCheckedSerializationMap(sessionMap, true);
+ }
+
+ if (checkApplication)
+ {
+ applicationMap = CollectionUtils.getCheckedSerializationMap(applicationMap, false);
+ }
}
- }
+
+ _sessionMap = sessionMap;
+ _applicationMap = applicationMap;
+ }
- protected ExternalContext getExternalContext()
+ /**
+ * Unregisters the checking of the specified session attribute
+ * @param external ExternalContext
+ * @param key Name of session attribute to unregister
+ */
+ public void unregisterSessionAttribute(ExternalContext external, String key)
{
- return _extContext;
+ SerializationChecker checker = SerializationChecker.getSerializationChecker(external, false);
+
+ if (checker != null)
+ {
+ if (_sessionBeanChecker != null)
+ {
+ _sessionBeanChecker.unregisterAttribute(key);
+ }
+ }
}
+ /**
+ * Unregisters the checking of the specified session attribute
+ * @param external ExternalContext
+ * @param key Name of session attribute to unregister
+ */
+ public void unregisterApplicationAttribute(ExternalContext external, String key)
+ {
+ SerializationChecker checker = SerializationChecker.getSerializationChecker(external, false);
+
+ if (checker != null)
+ {
+ if (_applicationBeanChecker != null)
+ {
+ _applicationBeanChecker.unregisterAttribute(key);
+ }
+ }
+ }
+
+ /**
+ * Return a wrapped HttpServletRequest if necessary to implement the checking features
+ * @param request
+ * @return
+ */
+ public HttpServletRequest getWrappedRequest(HttpServletRequest request)
+ {
+ if (_sessionBeanChecker != null)
+ {
+ return new SessionBeanTracker(request, _sessionBeanChecker, _sessionMap, _applicationMap);
+ }
+ else
+ {
+ return request;
+ }
+ }
+
+ /**
+ * Returns the potentially wrapped Session Map
+ * @return
+ */
public Map<String, Object> getSessionMap()
{
return _sessionMap;
}
+ /**
+ * Returns the potentially wrapped Application Map
+ * @return
+ */
public Map<String, Object> getApplicationMap()
{
return _applicationMap;
}
+
+ /**
+ * Check the session and application for mutations if configured to do so
+ */
+ public void checkForMutations()
+ {
+ if (_sessionBeanChecker != null)
+ _sessionBeanChecker.checkForMutations();
+
+ if (_applicationBeanChecker != null)
+ _applicationBeanChecker.checkForMutations();
+ }
- private final ExternalContext _extContext;
- private final Map _sessionMap;
- private final Map _applicationMap;
+ /**
+ * Wraps the HttpServletRequest so that we can return a wrapped Session so that we can catch
+ * changes to the Session attributes and/or return a wrapped ServletContext so that we can
+ * catch changes to the SevletContext attributes.
+ */
+ private static class SessionBeanTracker extends HttpServletRequestWrapper
+ {
+ public SessionBeanTracker(
+ HttpServletRequest request,
+ MutatedBeanChecker sessionBeanChecker,
+ Map<String, Object> sessionMap,
+ Map<String, Object> applicationMap)
+ {
+ super(request);
+
+ _wrappedSession = new SessionWrapper(request.getSession(),
+ sessionBeanChecker,
+ sessionMap,
+ applicationMap);
+ }
+
+ @Override
+ public HttpSession getSession()
+ {
+ return _wrappedSession;
+ }
+
+ @Override
+ public HttpSession getSession(boolean p1)
+ {
+ return _wrappedSession;
+ }
+
+ /**
+ * Wraps the HttpSession sso that we can catch
+ * changes to the Session attributes and/or return a wrapped ServletContext so that we can
+ * catch changes to the SevletContext attributes.
+ */
+ private static final class SessionWrapper implements HttpSession
+ {
+ SessionWrapper(
+ HttpSession session,
+ MutatedBeanChecker sessionChecker,
+ Map<String, Object> sessionMap,
+ Map<String, Object> applicationMap)
+ {
+ _delegate = session;
+ _sessionChecker = sessionChecker;
+ _sessionMap = sessionMap;
+ // determine whether we need to return a wrapped ServletContext as well
+ if (applicationMap != null)
+ {
+ _wrappedContext = new ContextWrapper(session.getServletContext(), applicationMap);
+ }
+ else
+ {
+ _wrappedContext = null;
+ }
+ }
+
+ public long getCreationTime()
+ {
+ return _delegate.getCreationTime();
+ }
+
+ public String getId()
+ {
+ return _delegate.getId();
+ }
+
+ public long getLastAccessedTime()
+ {
+ return _delegate.getLastAccessedTime();
+ }
+
+ public ServletContext getServletContext()
+ {
+ if (_wrappedContext != null)
+ {
+ return _wrappedContext;
+ }
+ else
+ {
+ return _delegate.getServletContext();
+ }
+ }
+
+ public void setMaxInactiveInterval(int maxInterval)
+ {
+ _delegate.setMaxInactiveInterval(maxInterval);
+ }
+
+ public int getMaxInactiveInterval()
+ {
+ return _delegate.getMaxInactiveInterval();
+ }
+
+ public HttpSessionContext getSessionContext()
+ {
+ return _delegate.getSessionContext();
+ }
+
+ public Object getAttribute(String attrName)
+ {
+ return _delegate.getAttribute(attrName);
+ }
+
+ public Object getValue(String attrName)
+ {
+ return _delegate.getValue(attrName);
+ }
+
+ public Enumeration getAttributeNames()
+ {
+ return _delegate.getAttributeNames();
+ }
+
+ public String[] getValueNames()
+ {
+ return _delegate.getValueNames();
+ }
+
+ /**
+ * Implement to delegate and inform the sessionChecker that the attribute is dirty
+ * @param key
+ * @param value
+ */
+ public void setAttribute(String key, Object value)
+ {
+ _delegate.setAttribute(key, value);
+ _sessionChecker.writeNotify(_sessionMap, key, value);
+ }
+
+ /**
+ * Implement to delegate and inform the sessionChecker that the attribute is dirty
+ * @param key
+ * @param value
+ */
+ public void putValue(String key, Object value)
+ {
+ _delegate.putValue(key, value);
+ _sessionChecker.writeNotify(_sessionMap, key, value);
+ }
+
+ /**
+ * Implement to delegate and inform the sessionChecker that the attribute is dirty
+ * @param key
+ */
+ public void removeAttribute(String key)
+ {
+ _delegate.removeAttribute(key);
+ _sessionChecker.removeNotify(_sessionMap, key);
+ }
+
+ /**
+ * Implement to delegate and inform the sessionChecker that the attribute is dirty
+ * @param key
+ */
+ public void removeValue(String key)
+ {
+ _delegate.removeValue(key);
+ _sessionChecker.removeNotify(_sessionMap, key);
+ }
+
+ /**
+ * Implement to delegate and inform the sessionChecker that all atrributes are dirty since
+ * the session has been blown away
+ */
+ public void invalidate()
+ {
+ _delegate.invalidate();
+ _sessionChecker.clearCheckedValues();
+ }
+
+ public boolean isNew()
+ {
+ return _delegate.isNew();
+ }
+
+ private final HttpSession _delegate;
+ private final MutatedBeanChecker _sessionChecker;
+ private final Map<String, Object> _sessionMap;
+ private final ServletContext _wrappedContext;
+ }
+
+ private final HttpSession _wrappedSession;
+ }
+
+ private final MutatedBeanChecker _sessionBeanChecker;
+ private final MutatedBeanChecker _applicationBeanChecker;
+
+ private final Map<String, Object> _sessionMap;
+ private final Map<String, Object> _applicationMap;
}
+
+ private static final String _CHECKED_MAPS_KEY = MutatedBeanChecker.class.getName() +"#MAPS";
+
+ private static final String _SERIALIZATION_CHECKER_KEY =
+ CheckSerializationConfigurator.class.getName() + "#CHECKER";
}
Modified: myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/GlobalConfiguratorImpl.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/GlobalConfiguratorImpl.java?rev=1028148&r1=1028147&r2=1028148&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/GlobalConfiguratorImpl.java (original)
+++ myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/GlobalConfiguratorImpl.java Thu Oct 28 00:39:07 2010
@@ -19,18 +19,16 @@
package org.apache.myfaces.trinidadinternal.config;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-
import java.util.concurrent.atomic.AtomicReference;
import javax.faces.context.ExternalContext;
import javax.servlet.ServletRequest;
-
import javax.servlet.ServletRequestWrapper;
-
import javax.servlet.http.HttpServletRequest;
import org.apache.myfaces.trinidad.config.Configurator;
@@ -40,6 +38,10 @@ import org.apache.myfaces.trinidad.conte
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.skin.SkinFactory;
import org.apache.myfaces.trinidad.util.ClassLoaderUtils;
+import org.apache.myfaces.trinidad.util.ComponentReference;
+import org.apache.myfaces.trinidad.util.ExternalContextUtils;
+import org.apache.myfaces.trinidad.util.RequestStateMap;
+import org.apache.myfaces.trinidad.util.RequestType;
import org.apache.myfaces.trinidadinternal.context.RequestContextFactoryImpl;
import org.apache.myfaces.trinidadinternal.context.external.ServletCookieMap;
import org.apache.myfaces.trinidadinternal.context.external.ServletRequestHeaderMap;
@@ -49,9 +51,6 @@ import org.apache.myfaces.trinidadintern
import org.apache.myfaces.trinidadinternal.context.external.ServletRequestParameterValuesMap;
import org.apache.myfaces.trinidadinternal.skin.SkinFactoryImpl;
import org.apache.myfaces.trinidadinternal.skin.SkinUtils;
-import org.apache.myfaces.trinidad.util.ExternalContextUtils;
-import org.apache.myfaces.trinidad.util.RequestStateMap;
-import org.apache.myfaces.trinidad.util.RequestType;
/**
* This is the implementation of the Trinidad's Global configurator. It provides the entry point for
@@ -409,8 +408,7 @@ public final class GlobalConfiguratorImp
}
finally
{
-
- //Do cleanup of anything which may have use the thread local manager durring
+ //Do cleanup of anything which may have use the thread local manager during
//init.
_releaseManagedThreadLocals();
}
@@ -480,12 +478,19 @@ public final class GlobalConfiguratorImp
RequestContext context = RequestContext.getCurrentInstance();
if (context != null)
{
+ // ensure that any deferred ComponentReferences are initialized
+ _finishComponentReferenceInitialization(ec);
+
context.release();
_releaseManagedThreadLocals();
+
assert RequestContext.getCurrentInstance() == null;
}
}
+ /**
+ * Ensure that any ThreadLocals initialized during this request are cleared
+ */
private void _releaseManagedThreadLocals()
{
ThreadLocalResetter resetter = _threadResetter.get();
@@ -496,6 +501,29 @@ public final class GlobalConfiguratorImp
}
}
+ /**
+ * Ensure that all DeferredComponentReferences are fully initialized before the
+ * request completes
+ */
+ private void _finishComponentReferenceInitialization(ExternalContext ec)
+ {
+ Map<String, Object> requestMap = ec.getRequestMap();
+
+ Collection<ComponentReference<?>> initializeList = (Collection<ComponentReference<?>>)
+ requestMap.get(_FINISH_INITIALIZATION_LIST_KEY);
+
+ if ((initializeList != null) && !initializeList.isEmpty())
+ {
+ for (ComponentReference<?> reference : initializeList)
+ {
+ reference.ensureInitialization();
+ }
+
+ // we've initialized everything, so we're done
+ initializeList.clear();
+ }
+ }
+
private void _endConfiguratorServiceRequest(final ExternalContext ec)
{
// Physical request has now ended
@@ -738,6 +766,10 @@ public final class GlobalConfiguratorImp
static private String _TEST_PARAM = TestRequest.class.getName() + ".TEST_PARAM";
}
+ // skanky duplication of key from ComponentReference Class
+ private static final String _FINISH_INITIALIZATION_LIST_KEY = ComponentReference.class.getName() +
+ "#FINISH_INITIALIZATION";
+
// hacky reference to the ThreadLocalResetter used to clean up request-scoped
// ThreadLocals
private AtomicReference<ThreadLocalResetter> _threadResetter =
Modified: myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/context/PageFlowScopeMap.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/context/PageFlowScopeMap.java?rev=1028148&r1=1028147&r2=1028148&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/context/PageFlowScopeMap.java (original)
+++ myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/context/PageFlowScopeMap.java Thu Oct 28 00:39:07 2010
@@ -368,7 +368,7 @@ class PageFlowScopeMap implements Map<St
static private TokenCache _getRootTokenCache(FacesContext context,
int lifetime)
{
- return TokenCache.getTokenCacheFromSession(context,
+ return TokenCache.getTokenCacheFromSession(context.getExternalContext(),
_PAGE_FLOW_SCOPE_CACHE,
true,
lifetime);
@@ -426,8 +426,8 @@ class PageFlowScopeMap implements Map<St
private String _token;
private HashMap<String, Object> _map;
- static private final String _PAGE_FLOW_SCOPE_CACHE =
+ private static final String _PAGE_FLOW_SCOPE_CACHE =
"org.apache.myfaces.trinidadinternal.application.PageFlowScope";
- static private final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(PageFlowScopeMap.class);
+ private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(PageFlowScopeMap.class);
private static final long serialVersionUID = 1L;
}
Modified: myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/SubKeyMap.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/SubKeyMap.java?rev=1028148&r1=1028147&r2=1028148&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/SubKeyMap.java (original)
+++ myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/SubKeyMap.java Thu Oct 28 00:39:07 2010
@@ -104,7 +104,7 @@ final public class SubKeyMap<V> extends
public Set<Map.Entry<String, V>> entrySet()
{
if (_entrySet == null)
- _entrySet = new Entries<V>();
+ _entrySet = new Entries();
return _entrySet;
}
@@ -131,7 +131,7 @@ final public class SubKeyMap<V> extends
//
// Set implementation for SubkeyMap.entrySet()
//
- private class Entries<V> extends AbstractSet<Map.Entry<String, V>>
+ private class Entries extends AbstractSet<Map.Entry<String, V>>
{
public Entries()
{
@@ -145,7 +145,7 @@ final public class SubKeyMap<V> extends
// exceptions. Consequently, gather the keys in a list
// and iterator over that.
List<String> keyList = _gatherKeys();
- return new EntryIterator<V>(keyList.iterator());
+ return new EntryIterator(keyList.iterator());
}
@Override
@@ -193,7 +193,7 @@ final public class SubKeyMap<V> extends
}
}
- private class EntryIterator<V> implements Iterator<Map.Entry<String, V>>
+ private class EntryIterator implements Iterator<Map.Entry<String, V>>
{
public EntryIterator(Iterator<String> iterator)
{
@@ -209,7 +209,7 @@ final public class SubKeyMap<V> extends
{
String baseKey = _iterator.next();
_currentKey = baseKey;
- return new Entry<V>(baseKey);
+ return new Entry(baseKey);
}
public void remove()
@@ -226,7 +226,7 @@ final public class SubKeyMap<V> extends
private String _currentKey;
}
- private class Entry<V> implements Map.Entry<String, V>
+ private class Entry implements Map.Entry<String, V>
{
public Entry(String baseKey)
{
Modified: myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java?rev=1028148&r1=1028147&r2=1028148&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java (original)
+++ myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java Thu Oct 28 00:39:07 2010
@@ -66,33 +66,51 @@ public class TokenCache implements Seria
*/
@SuppressWarnings("unchecked")
static public TokenCache getTokenCacheFromSession(
- FacesContext context,
- String cacheName,
- boolean createIfNeeded,
- int defaultSize)
- {
- ExternalContext external = context.getExternalContext();
- Object session = external.getSession(true);
- assert(session != null);
-
- TokenCache cache;
- // Synchronize on the session object to ensure that
- // we don't ever create two different caches
- synchronized (session)
+ ExternalContext extContext,
+ String cacheName,
+ boolean createIfNeeded,
+ int defaultSize)
+ {
+ Map<String, Object> sessionMap = extContext.getSessionMap();
+
+ TokenCache cache = (TokenCache)sessionMap.get(cacheName);
+
+ if (cache == null)
{
- cache = (TokenCache) external.getSessionMap().get(cacheName);
- if ((cache == null) && createIfNeeded)
+ if (createIfNeeded)
{
- // create the TokenCache with the crytographically random seed
- cache = new TokenCache(defaultSize, _getSeed());
-
- external.getSessionMap().put(cacheName, cache);
+ Object session = extContext.getSession(true);
+
+ // Synchronize on the session object to ensure that
+ // we don't ever create two different caches
+ synchronized (session)
+ {
+ cache = (TokenCache)sessionMap.get(cacheName);
+
+ if (cache == null)
+ {
+ // create the TokenCache with the crytographically random seed
+ cache = new TokenCache(defaultSize, _getSeed(), sessionMap, cacheName);
+
+ sessionMap.put(cacheName, cache);
+ }
+ else
+ {
+ // make sure the existing cache has its own attached so it can dirty itself
+ cache.reattachOwner(sessionMap);
+ }
+ }
}
}
+ else
+ {
+ // make sure the existing cache has its own attached so it can dirty itself
+ cache.reattachOwner(sessionMap);
+ }
return cache;
}
-
+
/**
* Returns a cryptographically secure random number to use as the TokenCache seed
*/
@@ -124,11 +142,10 @@ public class TokenCache implements Seria
/**
* For serialization only
*/
- public TokenCache()
+ TokenCache()
{
- this(_DEFAULT_SIZE, 0L);
- }
-
+ this(_DEFAULT_SIZE, 0L, null, null);
+ }
/**
* Create a TokenCache that will store the last "size" entries. This version should
@@ -138,30 +155,37 @@ public class TokenCache implements Seria
*/
public TokenCache(int size)
{
- this(size, 0L);
- }
-
- /**
- * Create a TokenCache that will store the last "size" entries,
- * and begins its tokens based on the seed (instead of always
- * starting at "0").
- * @Deprecated Use version using a long size instead for greater security
- */
- public TokenCache(int size, int seed)
- {
- this(size, (long)seed);
+ this(size, 0L, null, null);
}
/**
* Create a TokenCache that will store the last "size" entries,
* and begins its tokens based on the seed (instead of always
* starting at "0").
+ * @patam owner Optional Cache that stores the token cache
+ * @param keyInOwner Optional Name under which this cache is stored in the owner
*/
- public TokenCache(int size, long seed)
+ private TokenCache(int size, long seed, Map<String, Object> owner, String keyInOwner)
+ {
+ _cache = new LRU(size);
+ _pinned = new ConcurrentHashMap<String, String>(size);
+ _count = new AtomicLong(seed);
+ _owner = owner;
+ _keyInOwner = keyInOwner;
+ }
+
+ /**
+ * Reattaches the owner after Serialization since the owner might not be Serializable or it
+ * might not be a good idea to serialize the owner.
+ * @param owner
+ * @throws NullPointerException if owner is null
+ */
+ public void reattachOwner(Map<String, Object> owner)
{
- _cache = new LRU(size);
- _pinned = new ConcurrentHashMap<String, String>(size);
- _count = new AtomicLong(seed);
+ if (owner == null)
+ throw new NullPointerException("Can't set owner to null");
+
+ _owner = owner;
}
/**
@@ -222,6 +246,9 @@ public class TokenCache implements Seria
targetStore.put(token, value);
+ // our contents have changed, so mark ourselves as dirty in our owner
+ _dirty();
+
return token;
}
@@ -289,14 +316,22 @@ public class TokenCache implements Seria
String token,
Map<String, V> targetStore)
{
+ V oldValue;
+
synchronized (this)
{
_LOG.finest("Removing token {0} from cache", token);
_cache.remove(token);
+
// TODO: should removing a value that is "pinned" take?
// Or should it stay in memory?
- return _removeTokenIfReady(targetStore, token);
+ oldValue = _removeTokenIfReady(targetStore, token);
}
+
+ // our contents have changed, so mark ourselves as dirty in our owner
+ _dirty();
+
+ return oldValue;
}
/**
@@ -314,6 +349,9 @@ public class TokenCache implements Seria
_cache.clear();
}
+
+ // our contents have changed, so mark ourselves as dirty in our owner
+ _dirty();
}
private String _getNextToken()
@@ -324,6 +362,17 @@ public class TokenCache implements Seria
// convert using base 36 because it is a fast efficient subset of base-64
return Long.toString(nextToken, 36);
}
+
+ /**
+ * Mark the cache as dirty in the owner
+ */
+ private void _dirty()
+ {
+ if (_keyInOwner != null)
+ {
+ _owner.put(_keyInOwner, this);
+ }
+ }
private class LRU extends LRUCache<String, String>
{
@@ -351,10 +400,15 @@ public class TokenCache implements Seria
// the current token value
private final AtomicLong _count;
+ private final String _keyInOwner;
+
// Hack instance parameter used to communicate between the LRU cache's
// removing() method, and the addNewEntry() method that may trigger it
private transient String _removed;
+ // owning cache
+ private transient Map<String, Object> _owner;
+
static private final int _DEFAULT_SIZE = 15;
static private final long serialVersionUID = 1L;
static private final TrinidadLogger _LOG =
Modified: myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/webapp/TrinidadFilterImpl.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/webapp/TrinidadFilterImpl.java?rev=1028148&r1=1028147&r2=1028148&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/webapp/TrinidadFilterImpl.java (original)
+++ myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/webapp/TrinidadFilterImpl.java Thu Oct 28 00:39:07 2010
@@ -39,11 +39,14 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
import org.apache.myfaces.trinidad.context.RequestContext;
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.RequestStateMap;
+import org.apache.myfaces.trinidadinternal.config.CheckSerializationConfigurator;
import org.apache.myfaces.trinidadinternal.config.GlobalConfiguratorImpl;
import org.apache.myfaces.trinidadinternal.config.dispatch.DispatchResponseConfiguratorImpl;
import org.apache.myfaces.trinidadinternal.config.dispatch.DispatchServletResponse;
@@ -94,10 +97,14 @@ public class TrinidadFilterImpl implemen
public void init(FilterConfig filterConfig) throws ServletException
{
+ // potentially wrap the FilterConfig to catch Serialization changes
+ filterConfig = CheckSerializationConfigurator.getFilterConfig(filterConfig);
+
_servletContext = filterConfig.getServletContext();
//There is some functionality that still might require servlet-only filter services.
_filters = ClassLoaderUtils.getServices(TrinidadFilterImpl.class.getName());
+
for(Filter f:_filters)
{
f.init(filterConfig);
@@ -130,7 +137,20 @@ public class TrinidadFilterImpl implemen
// properly installed.
request.setAttribute(_FILTER_EXECUTED_KEY, Boolean.TRUE);
- ExternalContext externalContext = new ServletExternalContext(_servletContext, request, response);
+ // potentially wrap the request in order to check managed bean HA
+ if (request instanceof HttpServletRequest)
+ {
+ request = CheckSerializationConfigurator.getHttpServletRequest(
+ new ServletExternalContext(_servletContext, request, response),
+ (HttpServletRequest)request);
+ }
+
+ // potentially wrap the ServletContext in order to check managed bean HA
+ ExternalContext externalContext = new ServletExternalContext(
+ _getPotentiallyWrappedServletContext(request),
+ request,
+ response);
+
GlobalConfiguratorImpl config = GlobalConfiguratorImpl.getInstance();
config.beginRequest(externalContext);
@@ -207,7 +227,11 @@ public class TrinidadFilterImpl implemen
{
// -= Scott O'Bryan =-
// Added for backward compatibility
- ExternalContext ec = new ServletExternalContext(_servletContext, request, response);
+ // potentially wrap the ServletContext to check ManagerBean HA
+ ExternalContext ec = new ServletExternalContext(_getPotentiallyWrappedServletContext(request),
+ request,
+ response);
+
boolean isHttpReq = ExternalContextUtils.isHttpServletRequest(ec);
if(isHttpReq)
@@ -384,6 +408,7 @@ public class TrinidadFilterImpl implemen
{
return _launchParam;
}
+
private static final long serialVersionUID = 1L;
}
@@ -422,6 +447,24 @@ public class TrinidadFilterImpl implemen
}
}
+ /**
+ * Returns a potentially wrapped ServletContext for ManagedBean HA
+ */
+ private ServletContext _getPotentiallyWrappedServletContext(ServletRequest request)
+ {
+ if (request instanceof HttpServletRequest)
+ {
+ HttpSession session = ((HttpServletRequest)request).getSession(false);
+
+ if (session != null)
+ {
+ return session.getServletContext();
+ }
+ }
+
+ return _servletContext;
+ }
+
private ServletContext _servletContext;
private List<Filter> _filters = null;
Modified: myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/xrts/org/apache/myfaces/trinidadinternal/resource/LoggerBundle.xrts
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/xrts/org/apache/myfaces/trinidadinternal/resource/LoggerBundle.xrts?rev=1028148&r1=1028147&r2=1028148&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/xrts/org/apache/myfaces/trinidadinternal/resource/LoggerBundle.xrts (original)
+++ myfaces/trinidad/branches/1.2.12.3-branch/trinidad-impl/src/main/xrts/org/apache/myfaces/trinidadinternal/resource/LoggerBundle.xrts Thu Oct 28 00:39:07 2010
@@ -1080,4 +1080,14 @@ The skin {0} specified on the requestMap
<resource key="INVALID_LOCALE_VARIANT_HAS_SLASH">Invalid variant for Locale identifier {0} - cannot contain slashes to avoid XSS attack. Will use empty string for variant.</resource>
<resource key="COULD_NOT_DELETE_FILE">Could not delete the file {0}</resource>
+
+<!-- ATTRIBUTE_SERIALIZATION_FAILED -->
+<resource key="ATTRIBUTE_SERIALIZATION_FAILED">Error serializing {0} attribute:{1} value:{2}</resource>
+
+<!-- ATTRIBUTE_NOT_SERIALIABLE -->
+<resource key="ATTRIBUTE_NOT_SERIALIABLE">Failover error: {0} attribute:{1} of type {2} is not Serializable</resource>
+
+<!-- SERIALIZABLE_ATTRIBUTE_MUTATED -->
+<resource key="SERIALIZABLE_ATTRIBUTE_MUTATED">Failover error: Serialization of {0} attribute:{1} has changed from {2} to {3} without the attribute being dirtied</resource>
+
</resources>