You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ma...@apache.org on 2009/01/30 15:15:24 UTC
svn commit: r739285 - in /myfaces/trinidad/branches/1.2.10.1-branch:
trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/
trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/
Author: matzew
Date: Fri Jan 30 14:15:23 2009
New Revision: 739285
URL: http://svn.apache.org/viewvc?rev=739285&view=rev
Log:
TRINIDAD-1356 - Application of changes by the ChangeManager is extremely slow
TRINIDAD-1375 - Increase strength of viewState token
thanks to Blake Sullivan for the patches
Modified:
myfaces/trinidad/branches/1.2.10.1-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/MoveChildComponentChange.java
myfaces/trinidad/branches/1.2.10.1-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java
myfaces/trinidad/branches/1.2.10.1-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java
Modified: myfaces/trinidad/branches/1.2.10.1-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/MoveChildComponentChange.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.10.1-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/MoveChildComponentChange.java?rev=739285&r1=739284&r2=739285&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.10.1-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/MoveChildComponentChange.java (original)
+++ myfaces/trinidad/branches/1.2.10.1-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/MoveChildComponentChange.java Fri Jan 30 14:15:23 2009
@@ -1,5 +1,6 @@
package org.apache.myfaces.trinidad.change;
+import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
@@ -92,6 +93,7 @@
_commonParent);
_destinationContainerScopedId =
ComponentUtils.getScopedIdForComponent(destinationContainer, _commonParent);
+
_commonParentScopedId =
ComponentUtils.getScopedIdForComponent(_commonParent, null);
@@ -102,11 +104,58 @@
throw new IllegalArgumentException(
_LOG.getMessage("MOVE_PARTICIPANTS_WITHOUT_ID"));
+ // calculate the absolute scoped ids for the source and destination so that we can
+ // handle remapping scoped ids in the SessionChangeManager
+ String commonParentPrefix = _getScopedIdPrefix(_commonParent, _commonParentScopedId);
+
+ _sourceAbsoluteScopedId = (commonParentPrefix != null)
+ ? new StringBuilder(commonParentPrefix).
+ append(NamingContainer.SEPARATOR_CHAR).
+ append(_movableChildScopedId).toString()
+ : _movableChildScopedId;
+
+ // calculate the absolute scoped id of the destination
+ String destinationContainerPrefix = _getScopedIdPrefix(destinationContainer,
+ _destinationContainerScopedId);
+
+ StringBuilder destinationScopedIdBuilder = new StringBuilder();
+
+ if (commonParentPrefix != null)
+ {
+ destinationScopedIdBuilder.append(commonParentPrefix).append(NamingContainer.SEPARATOR_CHAR);
+ }
+
+ if (destinationContainerPrefix != null)
+ {
+ destinationScopedIdBuilder.append(destinationContainerPrefix).append(NamingContainer.SEPARATOR_CHAR);
+ }
+
+ _destinationAbsoluteScopedId = destinationScopedIdBuilder.append(movableChild.getId()).toString();
+
// For insertBeforeComponent, we do not care to obtain scoped id.
_insertBeforeId = (insertBeforeComponent == null) ?
null:insertBeforeComponent.getId();
}
+ private String _getScopedIdPrefix(UIComponent component, String scopedId)
+ {
+ if (component instanceof NamingContainer)
+ return scopedId;
+ else
+ {
+ // remove the component's id from the end
+ int separatorIndex = scopedId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
+
+ if (separatorIndex >= 0)
+ return scopedId.substring(0, separatorIndex);
+ else
+ {
+ // component was at top level
+ return null;
+ }
+ }
+ }
+
/**
* Convenience method to add this MoveChildComponentChange to the supplied
* ChangeManager. The change will be registered against a parent component
@@ -388,6 +437,23 @@
}
/**
+ * Returns the absolute scopedId of the source component
+ */
+ public String getSourceScopedId()
+ {
+ return _sourceAbsoluteScopedId;
+ }
+
+
+ /**
+ * Returns the absolute scopedId of the source component at its destination
+ */
+ public String getDestinationScopedId()
+ {
+ return _destinationAbsoluteScopedId;
+ }
+
+ /**
* Returns the depth of a UIComponent in the tree.
* @param comp the UIComponent whose depth has to be calculated
* @return the depth of the passed in UIComponent
@@ -427,7 +493,8 @@
private final String _destinationContainerScopedId;
private final String _commonParentScopedId;
private final String _insertBeforeId;
-
+ private final String _sourceAbsoluteScopedId;
+ private final String _destinationAbsoluteScopedId;
private static final long serialVersionUID = 1L;
private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
Modified: myfaces/trinidad/branches/1.2.10.1-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.10.1-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java?rev=739285&r1=739284&r2=739285&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.10.1-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java (original)
+++ myfaces/trinidad/branches/1.2.10.1-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java Fri Jan 30 14:15:23 2009
@@ -20,10 +20,15 @@
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
+import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.faces.component.UIComponent;
@@ -31,6 +36,7 @@
import javax.faces.context.FacesContext;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
+import org.apache.myfaces.trinidad.util.CollectionUtils;
import org.apache.myfaces.trinidad.util.ComponentUtils;
import org.w3c.dom.Document;
@@ -53,13 +59,15 @@
{
UIViewRoot viewRoot = facesContext.getViewRoot();
- List<QualifiedComponentChange> componentChangesForView =
- _getComponentChangesForView(facesContext, viewRoot.getViewId(), false);
+ // retrieve the ComponentChanges for this current viewid
+ ChangesForView changesForView = _getChangesForView(facesContext, viewRoot.getViewId(), false);
- for (QualifiedComponentChange qualifiedChange : componentChangesForView)
+ // loop through the viewId's changes, applying the changes
+ for (QualifiedComponentChange qualifiedChange : changesForView.getComponentChangesForView())
{
UIComponent targetComponent =
viewRoot.findComponent(qualifiedChange.getTargetComponentScopedId());
+
// Possible that the target component no more exists in the view
if (targetComponent != null)
{
@@ -92,15 +100,90 @@
{
String viewId = facesContext.getViewRoot().getViewId();
- List<QualifiedComponentChange> componentChangesForView =
- _getComponentChangesForView(facesContext, viewId, true);
+ // get the ComponentChanges for the current viewId
+ ChangesForView changesForView = _getChangesForView(facesContext, viewId, true);
+ // get the absolute scopedId for the target component so that we have a unique identifier
+ // to compare
String scopedIdForTargetComponent =
- ComponentUtils.getScopedIdForComponent(targetComponent, null);
+ ComponentUtils.getScopedIdForComponent(targetComponent, null);
- componentChangesForView.add(
- new QualifiedComponentChange(scopedIdForTargetComponent,
- componentChange));
+ // try to collapse AttributeComponentChanges, handling component movement so that
+ // we can collapse any attribute change on the same component
+ if (componentChange instanceof AttributeComponentChange)
+ {
+ AttributeComponentChange attributeChange = (AttributeComponentChange)componentChange;
+ String attributeName = attributeChange.getAttributeName();
+
+ // would really rather use a Deque here and iterate backwards, which would also make
+ // handling the rename changes easier
+ Iterator<QualifiedComponentChange> changes =
+ changesForView.getComponentChangesForView().iterator();
+
+ // list of changes that have renamed the scoped id of this component. We need to
+ // handle this aliasing when traversing through the changes looking for matches
+ Iterator<MoveChildComponentChange> renameChanges =
+ changesForView.getRenameChanges(scopedIdForTargetComponent);
+
+ // we need to look through the rename list to map from the current names to
+ // the new names
+ MoveChildComponentChange nextRenameChange;
+ String currTargetScopedId;
+
+ if (renameChanges.hasNext())
+ {
+ // we have at least one rename change, so get it and find the name that this
+ // component was originally known by
+ nextRenameChange = renameChanges.next();
+ currTargetScopedId = nextRenameChange.getSourceScopedId();
+ }
+ else
+ {
+ nextRenameChange = null;
+ currTargetScopedId = scopedIdForTargetComponent;
+ }
+
+ // loop forward through the changes looking for AttributeChanges to collapse
+ while (changes.hasNext())
+ {
+ QualifiedComponentChange currQualifiedChange = changes.next();
+
+ if (currQualifiedChange.getComponentChange() == nextRenameChange)
+ {
+ // we got a match, so update the scoped id we should be looking for
+ currTargetScopedId = nextRenameChange.getDestinationScopedId();
+
+ nextRenameChange = (renameChanges.hasNext())
+ ? renameChanges.next()
+ : null;
+ }
+ else if (currQualifiedChange.getTargetComponentScopedId().equals(currTargetScopedId))
+ {
+ // found a change on the same component. Check if it's an AttributeChange
+ ComponentChange currChange = currQualifiedChange.getComponentChange();
+
+ if (currChange instanceof AttributeComponentChange)
+ {
+ AttributeComponentChange currAttributeChange = (AttributeComponentChange)currChange;
+
+ // Check if the AttributeChange is for the same attribute
+ if (attributeName.equals(currAttributeChange.getAttributeName()))
+ {
+ // the old AttributeChange is for the same attribute, so remove it since the
+ // new AttributeChange would eclipse it anyway.
+ changes.remove();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ QualifiedComponentChange newQualifiedChange = new QualifiedComponentChange(
+ scopedIdForTargetComponent,
+ componentChange);
+
+ changesForView.addChange(newQualifiedChange);
}
/**
@@ -118,11 +201,12 @@
* @param viewId The id of the view for which changes are required.
* @param createIfNecessary Indicates whether the underlying datastructures
* that store the component changes needs to be created if absent.
- * @return The in-order list of component changes for the supplied view. This
+ * @return The ChangesForView object containing information about the changes for the specified
+ * viewId, including in-order list of component changes for the supplied view. This
* will be in the same order in which the component changes were added through
* calls to <code>addComponentChange()</code>.
*/
- private List<QualifiedComponentChange> _getComponentChangesForView(
+ private ChangesForView _getChangesForView(
FacesContext facesContext,
String viewId,
boolean createIfNecessary)
@@ -130,25 +214,22 @@
Object session = facesContext.getExternalContext().getSession(true);
// Key is view id and value is list of component changes for that view
- ConcurrentHashMap<String, List<QualifiedComponentChange>>
- componentChangesMapForSession;
+ ConcurrentHashMap<String, ChangesForView> componentChangesMapForSession;
synchronized(session)
{
Map<String, Object> sessMap =
facesContext.getExternalContext().getSessionMap();
- componentChangesMapForSession =
- (ConcurrentHashMap<String, List<QualifiedComponentChange>>)
- (sessMap.get(_COMPONENT_CHANGES_MAP_FOR_SESSION_KEY));
+ componentChangesMapForSession = (ConcurrentHashMap<String, ChangesForView>)
+ sessMap.get(_COMPONENT_CHANGES_MAP_FOR_SESSION_KEY);
if (componentChangesMapForSession == null)
{
if (!createIfNecessary)
- return Collections.emptyList();
+ return _EMPTY_CHANGES;
- componentChangesMapForSession =
- new ConcurrentHashMap<String, List<QualifiedComponentChange>>();
+ componentChangesMapForSession = new ConcurrentHashMap<String, ChangesForView>();
sessMap.put(_COMPONENT_CHANGES_MAP_FOR_SESSION_KEY,
componentChangesMapForSession);
}
@@ -157,19 +238,124 @@
if (!componentChangesMapForSession.containsKey(viewId))
{
if (!createIfNecessary)
- return Collections.emptyList();
+ return _EMPTY_CHANGES;
- // Writes are per change addition, not very frequent, using
- // CopyOnWriteArrayList should suffice.
- componentChangesMapForSession.putIfAbsent(
- viewId,
- new CopyOnWriteArrayList<QualifiedComponentChange>());
+ componentChangesMapForSession.putIfAbsent(viewId, new ChangesForView(true));
}
return componentChangesMapForSession.get(viewId);
}
- private static final class QualifiedComponentChange implements Serializable
+ /**
+ * Tracks the component changes for a particular view as well as all the movement
+ * changes so that component aliasing can be tracked
+ */
+ private static final class ChangesForView implements Serializable
+ {
+ protected ChangesForView(boolean rw)
+ {
+ if (rw)
+ {
+ _componentChangesForView = new ConcurrentLinkedQueue<QualifiedComponentChange>();
+ _renameChanges = new CopyOnWriteArrayList<MoveChildComponentChange>();
+ }
+ else
+ {
+ _componentChangesForView = CollectionUtils.emptyQueue();
+ _renameChanges = null;
+ }
+ }
+
+ /**
+ * Returns the QualifiedComponentChanges for this viewId
+ */
+ protected Iterable<QualifiedComponentChange> getComponentChangesForView()
+ {
+ return _componentChangesForView;
+ }
+
+ /**
+ * Adds a change to the QualifiedComponentChanges for this viewId, handling
+ * MoveChildComponentChanges specially to handle cases where the clientId
+ * of a component changes as a result of a rename operation
+ */
+ protected void addChange(QualifiedComponentChange qualifiedChange)
+ {
+ _componentChangesForView.add(qualifiedChange);
+
+ ComponentChange componentChange = qualifiedChange.getComponentChange();
+
+ if (componentChange instanceof MoveChildComponentChange)
+ {
+ // we only need to remove moves that actually changed the absolute scoped id of the
+ // component
+ MoveChildComponentChange moveComponentChange = (MoveChildComponentChange)componentChange;
+
+ if (!moveComponentChange.getSourceScopedId().equals(moveComponentChange.getDestinationScopedId()))
+ {
+ _renameChanges.add(moveComponentChange);
+ }
+ }
+ }
+
+ /**
+ * Returns the Iterator of rename changes that affect the current scoped id in ComponentChange order
+ * @return
+ */
+ protected Iterator<MoveChildComponentChange> getRenameChanges(String targetScopedId)
+ {
+ if (_renameChanges != null)
+ {
+ String currTargetScopedId = targetScopedId;
+ List renameChanges = null;
+
+ // iterate from the back of the List determining the MoveChildComponentChange
+ // that are aliased to this scoped id
+ ListIterator<MoveChildComponentChange> moveChanges =
+ _renameChanges.listIterator(_renameChanges.size());
+
+ while (moveChanges.hasPrevious())
+ {
+ MoveChildComponentChange currMoveChange = moveChanges.previous();
+
+ if (currTargetScopedId.equals(currMoveChange.getDestinationScopedId()))
+ {
+ // lazily create the list the first time we need it
+ if (renameChanges == null)
+ renameChanges = new ArrayList<MoveChildComponentChange>();
+
+ renameChanges.add(currMoveChange);
+
+ // get the new id to search for
+ currTargetScopedId = currMoveChange.getSourceScopedId();
+ }
+ }
+
+ if (renameChanges != null)
+ {
+ if (renameChanges.size() > 1)
+ {
+ // reverse the list to match the order that we will see these items when traversing
+ // the changes from the forward direction
+ Collections.reverse(renameChanges);
+ }
+
+ return renameChanges.iterator();
+ }
+ }
+
+ return CollectionUtils.emptyIterator();
+ }
+
+ private final Queue<QualifiedComponentChange> _componentChangesForView;
+ private final List<MoveChildComponentChange> _renameChanges;
+
+ private static final long serialVersionUID = 1L;
+ }
+
+ private static final ChangesForView _EMPTY_CHANGES = new ChangesForView(false);
+
+ private static class QualifiedComponentChange implements Serializable
{
public QualifiedComponentChange(String targetComponentScopedId,
ComponentChange componentChange)
@@ -198,7 +384,7 @@
private static final long serialVersionUID = 1L;
}
-
+
private static final String _COMPONENT_CHANGES_MAP_FOR_SESSION_KEY =
"org.apache.myfaces.trinidadinternal.ComponentChangesMapForSession";
Modified: myfaces/trinidad/branches/1.2.10.1-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.10.1-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java?rev=739285&r1=739284&r2=739285&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.10.1-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java (original)
+++ myfaces/trinidad/branches/1.2.10.1-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java Fri Jan 30 14:15:23 2009
@@ -18,19 +18,22 @@
*/
package org.apache.myfaces.trinidadinternal.util;
-import java.io.IOException;
-import java.io.ObjectInputStream;
import java.io.Serializable;
+import java.math.BigInteger;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
-import javax.servlet.http.HttpSession;
-
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
@@ -80,19 +83,8 @@
cache = (TokenCache) external.getSessionMap().get(cacheName);
if ((cache == null) && createIfNeeded)
{
- // Seed the token cache with the session ID (if available)
- int seed = 0;
- if (_USE_SESSION_TO_SEED_ID)
- {
- if (session instanceof HttpSession)
- {
- String id = ((HttpSession) session).getId();
- if (id != null)
- seed = id.hashCode();
- }
- }
-
- cache = new TokenCache(defaultSize, seed);
+ // create the TokenCache with the crytographically random seed
+ cache = new TokenCache(defaultSize, _getSeed());
external.getSessionMap().put(cacheName, cache);
}
@@ -100,6 +92,33 @@
return cache;
}
+
+ /**
+ * Returns a cryptographically secure random number to use as the TokenCache seed
+ */
+ private static long _getSeed()
+ {
+ SecureRandom rng;
+
+ try
+ {
+ // try SHA1 first
+ rng = SecureRandom.getInstance("SHA1PRNG");
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ // SHA1 not present, so try the default (which could potentially not be
+ // cryptographically secure)
+ rng = new SecureRandom();
+ }
+
+ // use 48 bits for strength and fill them in
+ byte[] randomBytes = new byte[6];
+ rng.nextBytes(randomBytes);
+
+ // convert to a long
+ return new BigInteger(randomBytes).longValue();
+ }
/**
@@ -107,28 +126,42 @@
*/
public TokenCache()
{
- this(_DEFAULT_SIZE, 0);
+ this(_DEFAULT_SIZE, 0L);
}
/**
- * Create a TokenCache that will store the last "size" entries.
+ * Create a TokenCache that will store the last "size" entries. This version should
+ * not be used if the token cache is externally accessible (since the seed always
+ * starts at 0). Use the
+ * constructor with the long seed instead.
*/
public TokenCache(int size)
{
- this(size, 0);
+ 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);
+ }
+
+ /**
+ * Create a TokenCache that will store the last "size" entries,
+ * and begins its tokens based on the seed (instead of always
+ * starting at "0").
+ */
+ public TokenCache(int size, long seed)
+ {
_cache = new LRU(size);
_pinned = new ConcurrentHashMap<String, String>(size);
- _count = seed;
+ _count = new AtomicLong(seed);
}
/**
@@ -285,19 +318,11 @@
private String _getNextToken()
{
- synchronized (_lock)
- {
- _count = _count + 1;
- return Integer.toString(_count, 16);
- }
- }
-
- private void readObject(ObjectInputStream in)
- throws ClassNotFoundException, IOException
- {
- in.defaultReadObject();
- // Re-create the lock, which was transient
- _lock = new Object();
+ // atomically increment the value
+ long nextToken = _count.incrementAndGet();
+
+ // convert using base 36 because it is a fast efficient subset of base-64
+ return Long.toString(nextToken, 36);
}
private class LRU extends LRUCache<String, String>
@@ -322,15 +347,14 @@
// stored, and the values are the tokens that are pinned. This is
// an N->1 ratio: the values may appear multiple times.
private final Map<String, String> _pinned;
- private int _count;
- private transient Object _lock = new Object();
+
+ // the current token value
+ private final AtomicLong _count;
// 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;
- static private final boolean _USE_SESSION_TO_SEED_ID = true;
-
static private final int _DEFAULT_SIZE = 15;
static private final long serialVersionUID = 1L;
static private final TrinidadLogger _LOG =