You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by aw...@apache.org on 2007/05/17 02:39:22 UTC

svn commit: r538778 - in /myfaces/trinidad/trunk/trinidad: trinidad-demo/src/main/webapp/WEB-INF/ trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/application/ trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/context/ trin...

Author: awiner
Date: Wed May 16 17:39:21 2007
New Revision: 538778

URL: http://svn.apache.org/viewvc?view=rev&rev=538778
Log:
TRINIDAD-21: dialog framework does not pin view state of launching page
- Enhance the TokenCache to support the concept of "pinned" tokens, which won't
  be flushed until everything that is pinning them down is gone
- Add a unit test for token cache to verify the above is working
- Add StateManagerImpl APIs to get the state token used for a request,
  and to store a token that should be "pinned" by the current request
- Add DialogServiceImpl APIs to stash off a state token for use
  in the dialog about to be launched and to pin that state token later,
  in each case using the StateManagerImpl APIs
- Call the DialogService CoreRenderKit to invoke the DialogServiceImpl APIs
  at the start and end of rendering
- Fix use of generics in LRUCache
- Add enough to the launchDialog demo to verify this is working

Added:
    myfaces/trinidad/trunk/trinidad/trinidad-impl/src/test/java/org/apache/myfaces/trinidadinternal/util/TokenCacheTest.java   (with props)
Modified:
    myfaces/trinidad/trunk/trinidad/trinidad-demo/src/main/webapp/WEB-INF/web.xml
    myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/application/StateManagerImpl.java
    myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/context/DialogServiceImpl.java
    myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/renderkit/core/CoreRenderKit.java
    myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/LRUCache.java
    myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java
    myfaces/trinidad/trunk/trinidad/trinidad-impl/src/test/java/org/apache/myfaces/trinidadinternal/renderkit/MRequestContext.java

Modified: myfaces/trinidad/trunk/trinidad/trinidad-demo/src/main/webapp/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad/trinidad-demo/src/main/webapp/WEB-INF/web.xml?view=diff&rev=538778&r1=538777&r2=538778
==============================================================================
--- myfaces/trinidad/trunk/trinidad/trinidad-demo/src/main/webapp/WEB-INF/web.xml (original)
+++ myfaces/trinidad/trunk/trinidad/trinidad-demo/src/main/webapp/WEB-INF/web.xml Wed May 16 17:39:21 2007
@@ -31,6 +31,14 @@
     <!--param-value>server</param-value-->
   </context-param>
 
+  <!-- Parameter to set the maximum number of client view state tokens.
+       Uncomment this to test low-token-count scenarios.
+  <context-param>
+    <param-name>org.apache.myfaces.trinidad.CLIENT_STATE_MAX_TOKENS</param-name>
+    <param-value>3</param-value>
+  </context-param>
+  -->
+
   <!-- Trinidad by default uses an optimized client-side state saving
        mechanism. To disable that, uncomment the following -->
   <!--context-param>
@@ -121,16 +129,6 @@
     <servlet-name>source</servlet-name>
     <servlet-class>org.apache.myfaces.trinidaddemo.webapp.SourceCodeServlet</servlet-class>
   </servlet>
-
-  <!-- On some version of OC4J, the following is needed when using jsf 1.0 -->
-  <!--servlet>
-    <servlet-name>jsp</servlet-name>
-    <servlet-class>oracle.jsp.runtimev2.JspServlet</servlet-class>
-    <init-param>
-      <param-name>tags_reuse_default</param-name>
-      <param-value>none</param-value>
-    </init-param>
-  </servlet-->
 
   <!-- Faces Servlet Mappings -->
   <servlet-mapping>

Modified: myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/application/StateManagerImpl.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/application/StateManagerImpl.java?view=diff&rev=538778&r1=538777&r2=538778
==============================================================================
--- myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/application/StateManagerImpl.java (original)
+++ myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/application/StateManagerImpl.java Wed May 16 17:39:21 2007
@@ -279,8 +279,13 @@
             // if this feature has not been disabled
             _useViewRootCache(context) ? root : null);
 
+        // See if we should pin this new state to any old state
+        String pinnedToken = (String)
+          context.getExternalContext().getRequestMap().get(_PINNED_STATE_TOKEN_KEY);
+
         token = cache.addNewEntry(pageState,
-                                  stateMap);
+                                  stateMap,
+                                  pinnedToken);
       }
       // If we got the "applicationViewCache", we're using it.
       else
@@ -307,6 +312,10 @@
       // Create a "tokenView" which abuses SerializedView to store
       // our token only
       view = new SerializedView(token, null);
+      
+      // And store the token for this request
+      context.getExternalContext().getRequestMap().put(_REQUEST_STATE_TOKEN_KEY,
+                                                       token);
     }
     else
     {
@@ -319,6 +328,29 @@
     return view;
   }
 
+  /**
+   * Requests that an old state token be "pinned" to the state of
+   * the current request.  This means that the view state corresponding
+   * to the token will not be released before the state for this request
+   * is released.
+   */
+  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);
+  }
+  
+  
   @Override
   public void writeState(FacesContext context,
                          SerializedView state) throws IOException
@@ -917,6 +949,11 @@
   private static final String _CACHED_SERIALIZED_VIEW =
     "org.apache.myfaces.trinidadinternal.application.CachedSerializedView";
 
+  private static final String _REQUEST_STATE_TOKEN_KEY =
+    "org.apache.myfaces.trinidadinternal.application.REQUEST_STATE_TOKEN";
+
+  private static final String _PINNED_STATE_TOKEN_KEY =
+    "org.apache.myfaces.trinidadinternal.application.PINNED_STATE_TOKEN";
 
 
   private static final String _APPLICATION_CACHE_TOKEN = "_a_";

Modified: myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/context/DialogServiceImpl.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/context/DialogServiceImpl.java?view=diff&rev=538778&r1=538777&r2=538778
==============================================================================
--- myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/context/DialogServiceImpl.java (original)
+++ myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/context/DialogServiceImpl.java Wed May 16 17:39:21 2007
@@ -283,8 +283,48 @@
     }
   }
 
+  /**
+   * Store the current state token (if a dialog has been launched).
+   */
+  static public void writeCurrentStateToken(FacesContext context, String token)
+  {
+    if (token == null)
+      return;
+
+    // Locate the String array on the request map, and store the token in
+    // there if it exists.  The String array is also going to be on the
+    // page flow scope map for the dialog
+    Object o =
+      context.getExternalContext().getRequestMap().get(_TARGET_FOR_STATE_TOKEN);
+    if (o instanceof String[])
+    {
+      String[] targetForToken = (String[]) o;
+      if (targetForToken.length == 1)
+        targetForToken[0] = token;
+    }
+  }
 
   /**
+   * Store the current state token (if a dialog has been launched).
+   */
+  static public void pinPriorState(FacesContext context)
+  {
+    RequestContext rc = RequestContext.getCurrentInstance();
+    Object o = rc.getPageFlowScope().get(_TARGET_FOR_STATE_TOKEN);
+    if (o instanceof String[])
+    {
+      String[] targetForToken = (String[]) o;
+      if (targetForToken.length == 1)
+      {
+        String token = targetForToken[0];
+        if (token != null)
+          StateManagerImpl.pinStateToRequest(context, token);
+      }
+    }
+  }
+  
+  
+  /**
    * Launch a dialog.
    * @todo Don't save parameters for state-saving, page-flow scope, etc.
    */
@@ -308,6 +348,13 @@
     // the renderkit to launch the dialog;  which means
     // we'll need to use the renderkit to close the dialog
     dialogParameters.put(_USED_RENDER_KIT_KEY, Boolean.TRUE);
+    
+    // We also need to pin down the current state token when we're
+    // inside the dialog.  But we don't actually know what the token
+    // will be until we're done rendering.  So leave a one-element
+    // String array that we can write to later.
+    String[] targetForToken = new String[1];
+    dialogParameters.put(_TARGET_FOR_STATE_TOKEN, targetForToken);
 
     // Try to launch a window using the render kit. If that
     // fails (or isn't needed), fall through to the same-window
@@ -327,6 +374,12 @@
       // And we must pop the pageFlow scope immediately;  it'll
       // be restored later.
       _context.getPageFlowScopeProvider().popPageFlowScope(context, false);
+
+      // We only need to pin the existing state when we're using the renderkit
+      // to launch the dialog - so only bother putting anything on the
+      // request in that case
+      context.getExternalContext().getRequestMap().put(_TARGET_FOR_STATE_TOKEN,
+                                                       targetForToken);
     }
     else
     {
@@ -454,6 +507,8 @@
     "org.apache.myfaces.trinidadinternal.DialogUsedRK";
   static private final String _RETURN_PARAM =
     "org.apache.myfaces.trinidadinternal.ReturnParam";
+  static private final String _TARGET_FOR_STATE_TOKEN =
+    "org.apache.myfaces.trinidadinternal.StateToken";
 
   static private final TrinidadLogger _LOG =
     TrinidadLogger.createTrinidadLogger(DialogServiceImpl.class);

Modified: myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/renderkit/core/CoreRenderKit.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/renderkit/core/CoreRenderKit.java?view=diff&rev=538778&r1=538777&r2=538778
==============================================================================
--- myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/renderkit/core/CoreRenderKit.java (original)
+++ myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/renderkit/core/CoreRenderKit.java Wed May 16 17:39:21 2007
@@ -61,7 +61,9 @@
 import org.apache.myfaces.trinidad.render.RenderUtils;
 import org.apache.myfaces.trinidadinternal.agent.AgentUtil;
 import org.apache.myfaces.trinidadinternal.agent.TrinidadAgent;
+import org.apache.myfaces.trinidadinternal.application.StateManagerImpl;
 import org.apache.myfaces.trinidadinternal.config.dispatch.DispatchResponseConfiguratorImpl;
+import org.apache.myfaces.trinidadinternal.context.DialogServiceImpl;
 import org.apache.myfaces.trinidadinternal.context.TrinidadPhaseListener;
 import org.apache.myfaces.trinidadinternal.io.DebugHtmlResponseWriter;
 import org.apache.myfaces.trinidadinternal.io.DebugResponseWriter;
@@ -415,6 +417,8 @@
   public void encodeBegin(FacesContext context)
   {
     /*CoreAdfRenderingContext arc = */new CoreRenderingContext();
+    // If there's any prior state, make sure our current "add" doesn't drop it
+    DialogServiceImpl.pinPriorState(context);
   }
 
 
@@ -431,6 +435,11 @@
    */
   public void encodeFinally(FacesContext context)
   {
+    // Get the state token from the StateManager (if one is available)
+    String stateToken = StateManagerImpl.getStateToken(context);
+    // And push it onto the DialogService (in case we launched anything)
+    DialogServiceImpl.writeCurrentStateToken(context, stateToken);
+    
     RenderingContext arc = RenderingContext.getCurrentInstance();
     if (arc != null)
     {

Modified: myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/LRUCache.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/LRUCache.java?view=diff&rev=538778&r1=538777&r2=538778
==============================================================================
--- myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/LRUCache.java (original)
+++ myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/LRUCache.java Wed May 16 17:39:21 2007
@@ -36,7 +36,7 @@
     _maxSize = maxSize;
   }
 
-  protected void removing(Object key)
+  protected void removing(K key)
   {
   }
 
@@ -45,7 +45,7 @@
   {
     if (size() > _maxSize)
     {
-      Object key = eldest.getKey();
+      K key = eldest.getKey();
       removing(key);
 
       _LOG.finer("Discarding cached value for key {0}", key);

Modified: myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java?view=diff&rev=538778&r1=538777&r2=538778
==============================================================================
--- myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java (original)
+++ myfaces/trinidad/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/util/TokenCache.java Wed May 16 17:39:21 2007
@@ -24,6 +24,8 @@
 
 import java.util.Map;
 
+import java.util.concurrent.ConcurrentHashMap;
+
 import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
 
@@ -33,7 +35,21 @@
 
 
 /**
- * A simple tokenized cache
+ * A simple LRU tokenized cache.  The cache is responsible for storing tokens,
+ * but the storage of the values referred to by those tokens is not handled
+ * by this class.  Instead, the user of this class has to provide a Map
+ * instance to each call.
+ * <p>
+ * The design seems odd, but is intentional - this way, a session Map can be used
+ * directly as the storage target for values, while the TokenCache simply maintains
+ * the logic of which tokens should still be available.  Storing values
+ * directly in the cache object (instead of directly on the session) causes
+ * HttpSession failover difficulties.
+ * <p>
+ * TokenCache also supports the concept of "pinning", whereby one token
+ * can be pinned to another.  The pinned token will not be removed from
+ * the cache until all tokens that it is pinned to are also out of the
+ * cache.
  */
 public class TokenCache implements Serializable
 {
@@ -103,26 +119,59 @@
     this(size, 0);
   }
 
+  /**
+   * 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, int seed)
   {
     _cache = new LRU(size);
+    _pinned = new ConcurrentHashMap<String, String>(size);
     _count = seed;
   }
 
   /**
    * Create a new token;  and use that token to store a value into
-   * a target Map.
+   * a target Map.  The least recently used values from the
+   * cache may be removed.
+   * @param value the value being added to the target store
+   * @param targetStore the map used for storing the value
+   * @return the token used to store the value
    */
   public String addNewEntry(
       Object value, 
       Map<String, Object> targetStore)
   {
-    Object remove = null;
+    return addNewEntry(value, targetStore, null);
+  }
+  
+  /**
+   * Create a new token;  and use that token to store a value into
+   * a target Map.  The least recently used values from the
+   * cache may be removed.
+   * @param value the value being added to the target store
+   * @param targetStore the map used for storing the value
+   * @param pinnedToken a token, that if still in the cache,
+   *    will not be freed until this current token is also freed
+   * @return the token used to store the value
+   */
+  public String addNewEntry(
+      Object value, 
+      Map<String, Object> targetStore,
+      String pinnedToken)
+  {
+    String remove = null;
     String token = null;
     synchronized (this)
     {
       token = _getNextToken();
 
+      // If there is a request to pin one token to another, 
+      // store that:  the pinnedToken is the value
+      if (pinnedToken != null)
+        _pinned.put(token, pinnedToken);
+      
       assert(_removed == null);
       // NOTE: this put() has a side-effect that can result
       // in _removed being non-null afterwards
@@ -134,8 +183,10 @@
     // This looks like "remove" must be null - given the
     // assert above.
     if (remove != null)
-      targetStore.remove(remove);
-
+    {
+      _removeTokenIfReady(targetStore, remove);
+    }
+    
     targetStore.put(token, value);
 
     return token;
@@ -143,17 +194,61 @@
 
 
   /**
-   * Returns true if an entry is still available.
+   * Returns true if an entry is still available.  This
+   * method has a side-effect:  by virtue of accessing the token,
+   * it is now at the top of the most-recently-used list.
    */
   public boolean isAvailable(String token)
   {
     synchronized (this)
     {
-      return _cache.get(token) != null;
+      // If the token is in the LRU cache, then it's available
+      if (_cache.get(token) != null)
+        return true;
+      
+      // And if the token is a value in "pinned", then it's also available
+      if (_pinned.containsValue(token))
+        return true;
+      
+      return false;
     }
   }
 
   /**
+   * Remove a token if is ready:  there are no pinned references to it.
+   * Note that it will be absent from the LRUCache.
+   */
+  synchronized private Object _removeTokenIfReady(
+      Map<String, Object> targetStore, 
+      String              token)
+  {
+    Object removedValue;
+    
+    // See if it's pinned to something still in memory
+    if (!_pinned.containsValue(token))
+    {
+      _LOG.finest("Removing token ''{0}''", token);
+      // Remove it from the target store
+      removedValue = targetStore.remove(token);
+      // Now, see if that key was pinning anything else
+      String wasPinned = _pinned.remove(token);
+      if (wasPinned != null)
+        // Yup, so see if we can remove that token
+        _removeTokenIfReady(targetStore, wasPinned);
+    }
+    else
+    {
+      _LOG.finest("Not removing pinned token ''{0}''", token);
+      // TODO: is this correct?  We're not really removing
+      // the target value.
+      removedValue = targetStore.get(token);
+    }
+    
+    return removedValue;
+  }
+
+
+  /**
    * Removes a value from the cache.
    * @return previous value associated with the token, if any
    */
@@ -165,7 +260,9 @@
     {
       _LOG.finest("Removing token {0} from cache", token);
       _cache.remove(token);
-      return targetStore.remove(token);
+      // TODO: should removing a value that is "pinned" take?
+      // Or should it stay in memory?
+      return _removeTokenIfReady(targetStore, token);
     }
   }
 
@@ -211,19 +308,24 @@
     }
 
     @Override
-    protected void removing(Object key)
+    protected void removing(String key)
     {
       _removed = key;
     }
   }
 
   private final Map<String, String> _cache;
+  
+  // Map from String to String, where the keys represent tokens that are
+  // 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();
 
   // Hack instance parameter used to communicate between the LRU cache's
   // removing() method, and the addNewEntry() method that may trigger it
-  private transient Object _removed;
+  private transient String _removed;
 
   static private final boolean _USE_SESSION_TO_SEED_ID = true;
 

Modified: myfaces/trinidad/trunk/trinidad/trinidad-impl/src/test/java/org/apache/myfaces/trinidadinternal/renderkit/MRequestContext.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad/trinidad-impl/src/test/java/org/apache/myfaces/trinidadinternal/renderkit/MRequestContext.java?view=diff&rev=538778&r1=538777&r2=538778
==============================================================================
--- myfaces/trinidad/trunk/trinidad/trinidad-impl/src/test/java/org/apache/myfaces/trinidadinternal/renderkit/MRequestContext.java (original)
+++ myfaces/trinidad/trunk/trinidad/trinidad-impl/src/test/java/org/apache/myfaces/trinidadinternal/renderkit/MRequestContext.java Wed May 16 17:39:21 2007
@@ -19,6 +19,7 @@
 package org.apache.myfaces.trinidadinternal.renderkit;
 
 import java.awt.Color;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -82,7 +83,7 @@
   @Override
   public Map<String, Object> getPageFlowScope()
   {
-    throw new UnsupportedOperationException("Not implemented yet");
+    return Collections.emptyMap();
   }
 
   @Override
@@ -301,4 +302,4 @@
   static private TimeZone _FIXED_TIME_ZONE =
     TimeZone.getTimeZone("America/Los_Angeles");
   
-}
\ No newline at end of file
+}

Added: myfaces/trinidad/trunk/trinidad/trinidad-impl/src/test/java/org/apache/myfaces/trinidadinternal/util/TokenCacheTest.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad/trinidad-impl/src/test/java/org/apache/myfaces/trinidadinternal/util/TokenCacheTest.java?view=auto&rev=538778
==============================================================================
--- myfaces/trinidad/trunk/trinidad/trinidad-impl/src/test/java/org/apache/myfaces/trinidadinternal/util/TokenCacheTest.java (added)
+++ myfaces/trinidad/trunk/trinidad/trinidad-impl/src/test/java/org/apache/myfaces/trinidadinternal/util/TokenCacheTest.java Wed May 16 17:39:21 2007
@@ -0,0 +1,273 @@
+/*
+ *  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.util;
+
+import junit.framework.TestCase;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * Test of TokenCache.
+ * NOTE: Calling TokenCache.isAvailable() effectively bumps
+ * an item up to the top of the most-recently-used list.
+ * So the calls to assertTrue(cache.isAvailable(...)) are
+ * not without side-effects, and you can in fact break
+ * the test by removing or changing the order of assert calls!
+ */
+public class TokenCacheTest extends TestCase
+{
+  public TokenCacheTest(String testName)
+  {
+    super(testName);
+  }
+
+  public void testBasic()
+  {
+    HashMap<String, Object> map = new HashMap<String, Object>();
+    // Add a pre-existing key to the test to verify that it is untouched
+    // Slight assumption here that this key would never get hit
+    map.put("this-would-never-be-used", 17);
+
+    TokenCache cache = new TokenCache(2);
+
+    // Add first value to cache
+    String token1 = cache.addNewEntry(1, map);
+    assertEquals(Integer.valueOf(1), map.get(token1));
+    assertEquals(2, map.size());
+    assertTrue(cache.isAvailable(token1));
+
+    // Add second value to cache
+    String token2 = cache.addNewEntry(2, map);
+    assertEquals(Integer.valueOf(1), map.get(token1));
+    assertEquals(Integer.valueOf(2), map.get(token2));
+    assertEquals(3, map.size());
+    assertTrue(cache.isAvailable(token1));
+    assertTrue(cache.isAvailable(token2));
+
+    // Add third value to cache - first value is now gone
+    String token3 = cache.addNewEntry(3, map);
+    assertNull(map.get(token1));
+    assertEquals(Integer.valueOf(2), map.get(token2));
+    assertEquals(Integer.valueOf(3), map.get(token3));
+    assertEquals(3, map.size());
+    assertFalse(cache.isAvailable(token1));
+    assertTrue(cache.isAvailable(token2));
+    assertTrue(cache.isAvailable(token3));
+
+    // Remove the third value from the cache
+    Object removed = cache.removeOldEntry(token3, map);
+    assertEquals(2, map.size());
+    assertFalse(cache.isAvailable(token3));
+    assertEquals(Integer.valueOf(3), removed);
+    assertEquals(Integer.valueOf(17), map.get("this-would-never-be-used"));
+    
+    // Clear the cache - only the non-cache value remains
+    cache.clear(map);
+    assertEquals(1, map.size());
+    assertEquals(Integer.valueOf(17), map.get("this-would-never-be-used"));
+  }
+
+  public void testIsAvailableAffectsOrder()
+  {
+    HashMap<String, Object> map = new HashMap<String, Object>();
+    TokenCache cache = new TokenCache(2);
+    String token1 = cache.addNewEntry(1, map);
+    String token2 = cache.addNewEntry(2, map);
+    // Check the availability of token1, which means that it
+    // is now more recently used than token2
+    assertTrue(cache.isAvailable(token1));
+
+    // Add token3:  we now have 1 and 3, not 2 and 3
+    String token3 = cache.addNewEntry(3, map);
+    assertTrue(cache.isAvailable(token1));
+    assertFalse(cache.isAvailable(token2));
+    assertTrue(cache.isAvailable(token3));
+  }
+
+  public void testPinned()
+  {
+    HashMap<String, Object> map = new HashMap<String, Object>();
+    // Add a pre-existing key to the test to verify that it is untouched
+    // Slight assumption here that this key would never get hit
+    map.put("this-would-never-be-used", 17);
+
+    TokenCache cache = new TokenCache(2);
+
+    // Add first value to cache
+    String token1 = cache.addNewEntry(1, map);
+    // Add second value to cache, but pin it to token1
+    String token2 = cache.addNewEntry(2, map, token1);
+
+    assertEquals(Integer.valueOf(1), map.get(token1));
+    assertEquals(Integer.valueOf(2), map.get(token2));
+    assertEquals(3, map.size());
+    assertTrue(cache.isAvailable(token1));
+    assertTrue(cache.isAvailable(token2));
+
+    // Add third value to cache - first value should still be present
+    // because it is pinned by token 2
+    String token3 = cache.addNewEntry(3, map);
+    assertEquals(Integer.valueOf(1), map.get(token1));
+    assertEquals(Integer.valueOf(2), map.get(token2));
+    assertEquals(Integer.valueOf(3), map.get(token3));
+    assertEquals(4, map.size());
+    assertTrue(cache.isAvailable(token1));
+    assertTrue(cache.isAvailable(token2));
+    assertTrue(cache.isAvailable(token3));
+
+    // Remove the second value from the cache, which
+    // should remove the first value as well
+    Object removed = cache.removeOldEntry(token2, map);
+    assertEquals(2, map.size());
+    assertFalse(cache.isAvailable(token1));
+    assertFalse(cache.isAvailable(token2));
+    assertTrue(cache.isAvailable(token3));
+    assertEquals(Integer.valueOf(2), removed);
+    assertEquals(Integer.valueOf(3), map.get(token3));
+    assertEquals(Integer.valueOf(17), map.get("this-would-never-be-used"));
+    
+    // Clear the cache - only the non-cache value remains
+    cache.clear(map);
+    assertEquals(1, map.size());
+    assertEquals(Integer.valueOf(17), map.get("this-would-never-be-used"));
+  }
+
+  public void testRecursivePinned()
+  {
+    HashMap<String, Object> map = new HashMap<String, Object>();
+    TokenCache cache = new TokenCache(2);
+
+    // Add first value to cache
+    String token1 = cache.addNewEntry(1, map);
+    // Add second value to cache, but pin it to token1
+    String token2 = cache.addNewEntry(2, map, token1);
+    // Add third value to cache, pinned to token2
+    String token3 = cache.addNewEntry(3, map, token2);
+    // Add fourth value to cache, pinned to token3
+    String token4 = cache.addNewEntry(4, map, token3);
+    assertEquals(4, map.size());
+
+    // Add fifth value to cache:  all should still be present,
+    // because 4 and 5 are the last two, and 3, 2, and 1 stay pinned
+    String token5 = cache.addNewEntry(5, map);
+    assertEquals(5, map.size());
+    assertTrue(cache.isAvailable(token1));
+    assertTrue(map.containsKey(token1));
+    assertTrue(cache.isAvailable(token2));
+    assertTrue(map.containsKey(token2));
+    assertTrue(cache.isAvailable(token3));
+    assertTrue(map.containsKey(token3));
+    assertTrue(cache.isAvailable(token4));
+    assertTrue(map.containsKey(token4));
+    assertTrue(cache.isAvailable(token5));
+    
+    // Add sixth value to cache:  only two should now be present
+    String token6 = cache.addNewEntry(6, map);
+    assertFalse(cache.isAvailable(token1));
+    assertFalse(map.containsKey(token1));
+    assertFalse(cache.isAvailable(token2));
+    assertFalse(map.containsKey(token2));
+    assertFalse(cache.isAvailable(token3));
+    assertFalse(map.containsKey(token3));
+    assertFalse(cache.isAvailable(token4));
+    assertFalse(map.containsKey(token4));
+    assertTrue(cache.isAvailable(token5));
+    assertTrue(cache.isAvailable(token6));
+    assertEquals(2, map.size());
+  }
+
+  public void testMultiplePinned()
+  {
+    HashMap<String, Object> map = new HashMap<String, Object>();
+    TokenCache cache = new TokenCache(2);
+
+    // Add first value to cache
+    String token1 = cache.addNewEntry(1, map);
+    // Add second value to cache, but pin it to token1
+    String token2 = cache.addNewEntry(2, map, token1);
+    // Add third value to cache, pinned to token1.
+    // All will be present, because 2 and 3 are the last
+    // two, but 1 is pinned.
+    String token3 = cache.addNewEntry(3, map, token1);
+    assertEquals(3, map.size());
+    assertTrue(cache.isAvailable(token1));
+    assertTrue(map.containsKey(token1));
+    assertTrue(cache.isAvailable(token2));
+    assertTrue(map.containsKey(token2));
+    assertTrue(cache.isAvailable(token3));
+    assertTrue(map.containsKey(token3));
+
+    // Add fourth value to cache, pinned to token1.
+    // Now should have 1, 3, and 4.
+    String token4 = cache.addNewEntry(4, map, token1);
+
+    assertEquals(3, map.size());
+    assertTrue(cache.isAvailable(token1));
+    assertTrue(map.containsKey(token1));
+    assertFalse(cache.isAvailable(token2));
+    assertFalse(map.containsKey(token2));
+    assertTrue(cache.isAvailable(token3));
+    assertTrue(map.containsKey(token3));
+    assertTrue(cache.isAvailable(token4));
+    assertTrue(map.containsKey(token4));
+
+    // Add fifth value to cache, unpinned:  now should have 1, 4 and 5
+    String token5 = cache.addNewEntry(5, map);
+    assertEquals(3, map.size());
+
+    assertTrue(cache.isAvailable(token1));
+    assertTrue(map.containsKey(token1));
+    assertFalse(cache.isAvailable(token2));
+    assertFalse(map.containsKey(token2));
+    assertFalse(cache.isAvailable(token3));
+    assertFalse(map.containsKey(token3));
+    assertTrue(cache.isAvailable(token4));
+    assertTrue(map.containsKey(token4));
+    assertTrue(cache.isAvailable(token5));
+
+    // Add sixth value, unpinned.  Should now only have 5 and 6
+    String token6 = cache.addNewEntry(6, map);
+    assertFalse(cache.isAvailable(token1));
+    assertFalse(map.containsKey(token1));
+    assertFalse(cache.isAvailable(token2));
+    assertFalse(map.containsKey(token2));
+    assertFalse(cache.isAvailable(token3));
+    assertFalse(map.containsKey(token3));
+    assertFalse(cache.isAvailable(token4));
+    assertFalse(map.containsKey(token4));
+    assertTrue(cache.isAvailable(token5));
+    assertTrue(cache.isAvailable(token6));
+  }
+
+  public void testPinnedInTinyCache()
+  {
+    HashMap<String, Object> map = new HashMap<String, Object>();
+    TokenCache cache = new TokenCache(1);
+    // Verify that adding a token that would have flushed an entry
+    // won't do so as long as it's pinning that entry
+    String token1 = cache.addNewEntry(1, map);
+    String token2 = cache.addNewEntry(2, map, token1);
+    String token3 = cache.addNewEntry(3, map, token2);
+    assertTrue(cache.isAvailable(token1));
+    assertTrue(cache.isAvailable(token2));    
+    assertTrue(cache.isAvailable(token3));    
+    assertEquals(3, map.size());
+  }
+}

Propchange: myfaces/trinidad/trunk/trinidad/trinidad-impl/src/test/java/org/apache/myfaces/trinidadinternal/util/TokenCacheTest.java
------------------------------------------------------------------------------
    svn:executable = *