You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by jo...@apache.org on 2008/04/02 08:01:36 UTC

svn commit: r643752 - in /cocoon/trunk/core/cocoon-sitemap: cocoon-sitemap-api/src/main/java/org/apache/cocoon/components/flow/ cocoon-sitemap-impl/src/changes/ cocoon-sitemap-impl/src/main/java/org/apache/cocoon/components/flow/

Author: joerg
Date: Tue Apr  1 23:01:30 2008
New Revision: 643752

URL: http://svn.apache.org/viewvc?rev=643752&view=rev
Log:
Fix synchronization issues in ContinuationsManager implementation.

Modified:
    cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-api/src/main/java/org/apache/cocoon/components/flow/ContinuationsManager.java
    cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-api/src/main/java/org/apache/cocoon/components/flow/WebContinuation.java
    cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-impl/src/changes/changes.xml
    cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-impl/src/main/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java

Modified: cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-api/src/main/java/org/apache/cocoon/components/flow/ContinuationsManager.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-api/src/main/java/org/apache/cocoon/components/flow/ContinuationsManager.java?rev=643752&r1=643751&r2=643752&view=diff
==============================================================================
--- cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-api/src/main/java/org/apache/cocoon/components/flow/ContinuationsManager.java (original)
+++ cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-api/src/main/java/org/apache/cocoon/components/flow/ContinuationsManager.java Tue Apr  1 23:01:30 2008
@@ -17,11 +17,12 @@
 package org.apache.cocoon.components.flow;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * The interface of the Continuations manager.
  *
- * The continuation manager maintains a forrest of {@link
+ * The continuation manager maintains a forest of {@link
  * WebContinuation} trees. Each tree defines the flow of control for a
  * user within the application.
  *
@@ -42,7 +43,7 @@
      * Create a <code>WebContinuation</code> object given a native
      * continuation object and its parent. If the parent continuation is
      * null, the <code>WebContinuation</code> returned becomes the root
-     * of a tree in the forrest.
+     * of a tree in the forest.
      *
      * @param kont an <code>Object</code> value
      * @param parentKont a <code>WebContinuation</code> value
@@ -82,18 +83,36 @@
      * @return a <code>WebContinuation</code> object, null if no such
      * <code>WebContinuation</code> could be found. Also null if 
      * <code>WebContinuation</code> was found but interpreter id does 
-     * not match the one that the continuation was initialy created for.
+     * not match the one that the continuation was initially created for.
      */
     public WebContinuation lookupWebContinuation(String id, String interpreterId);
 
     /**
      * Prints debug information about all web continuations into the log file.
-     * @see WebContinuation#display()
+     * 
+     * @deprecated Use {@link #getForest()}. This method will be removed from
+     * the interface.
      */
     public void displayAllContinuations();
     
     /**
      * Get a list of all continuations as <code>WebContinuationDataBean</code> objects. 
+     * 
+     * @deprecated Use {@link #getForest()}. This method will be removed.
      */
     public List getWebContinuationsDataBeanList();
+    
+    /**
+     * Get a set of all web continuations. The set itself will only contain the
+     * root continuations. Those will provide access to their children.
+     * 
+     * Since it should not be possible to mess up the actual managed
+     * continuations the returned list will contain clones of them.
+     * 
+     * The purpose of this method is clearly monitoring or for debugging the
+     * application. It has no direct relationship to functionality of the
+     * ContinuationsManager.
+     */
+    public Set getForest();
+    
 }

Modified: cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-api/src/main/java/org/apache/cocoon/components/flow/WebContinuation.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-api/src/main/java/org/apache/cocoon/components/flow/WebContinuation.java?rev=643752&r1=643751&r2=643752&view=diff
==============================================================================
--- cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-api/src/main/java/org/apache/cocoon/components/flow/WebContinuation.java (original)
+++ cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-api/src/main/java/org/apache/cocoon/components/flow/WebContinuation.java Tue Apr  1 23:01:30 2008
@@ -20,6 +20,7 @@
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -41,7 +42,7 @@
  * @since March 19, 2002
  * @version $Id$
  */
-public class WebContinuation implements Comparable {
+public class WebContinuation implements Comparable, Cloneable {
 
     /**
      * The continuation this object represents.
@@ -399,7 +400,30 @@
             getParentContinuation().getChildren().remove(this);
         }
     }
-    
+
+    /**
+     * Creates a clone of this WebContinuation without trying to clone the actual continuation, the
+     * user object or the disposer.
+     * 
+     * TODO: Check continuation, user object, disposer for implementing {@link Cloneable} or
+     *       {@link java.io.Serializable}.
+     */
+    public Object clone() {
+        
+        WebContinuation clone = new WebContinuation(id, continuation, null, timeToLive, interpreterId, disposer);
+        // reset last access time
+        clone.lastAccessTime = this.lastAccessTime;
+        // recreate hierarchy recursively
+        for (Iterator iter = this.children.iterator(); iter.hasNext();) {
+            WebContinuation child = (WebContinuation) iter.next();
+            WebContinuation childClone = (WebContinuation) child.clone();
+            // relationships must be fixed manually
+            childClone.parentContinuation = clone;
+            clone.children.add(childClone);
+        }
+        return clone;
+    }
+
     /**
      * Debugging method.
      *

Modified: cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-impl/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-impl/src/changes/changes.xml?rev=643752&r1=643751&r2=643752&view=diff
==============================================================================
--- cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-impl/src/changes/changes.xml (original)
+++ cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-impl/src/changes/changes.xml Tue Apr  1 23:01:30 2008
@@ -26,6 +26,9 @@
 <document>
   <body>
     <release version="1.0.1" date="TBA" description="unreleased">
+      <action dev="joerg" type="fix">
+        Fix synchronization issues in ContinuationsManager implementation.
+      </action>
       <action dev="joerg" type="fix" fixes-bug="COCOON-2109" due-to="Miguel Cuervo" due-to-email="miguel.cuervo@cgi.com">
         Fix clean up of continuations.
       </action>

Modified: cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-impl/src/main/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-impl/src/main/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java?rev=643752&r1=643751&r2=643752&view=diff
==============================================================================
--- cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-impl/src/main/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java (original)
+++ cocoon/trunk/core/cocoon-sitemap/cocoon-sitemap-impl/src/main/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java Tue Apr  1 23:01:30 2008
@@ -27,6 +27,7 @@
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+
 import javax.servlet.http.HttpSession;
 import javax.servlet.http.HttpSessionBindingEvent;
 import javax.servlet.http.HttpSessionBindingListener;
@@ -46,6 +47,10 @@
 import org.apache.cocoon.environment.Request;
 import org.apache.cocoon.thread.RunnableManager;
 import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.cocoon.util.Deprecation;
+
+import org.apache.commons.collections.Predicate;
+import org.apache.commons.collections.iterators.FilterIterator;
 
 
 /**
@@ -69,50 +74,38 @@
  */
 public class ContinuationsManagerImpl extends AbstractLogEnabled
                                       implements ContinuationsManager, Configurable, ThreadSafe,
-                                                 Serviceable, Contextualizable  {
+                                                 Serviceable, Contextualizable {
 
-    static final int CONTINUATION_ID_LENGTH = 20;
-    static final String EXPIRE_CONTINUATIONS = "expire-continuations";
+    private static final int CONTINUATION_ID_LENGTH = 20;
 
     /**
      * Random number generator used to create continuation ID
      */
     protected SecureRandom random;
-    protected byte[] bytes;
+    protected final byte[] bytes = new byte[CONTINUATION_ID_LENGTH];
 
     /**
-     * How long does a continuation exist in memory since the last
-     * access? The time is in milliseconds, and the default is 1 hour.
+     * Sorted set of <code>WebContinuation</code> instances, based on
+     * their expiration time. This is used by the background thread to
+     * invalidate continuations.
      */
-    protected int defaultTimeToLive;
+    protected final SortedSet expirations = Collections.synchronizedSortedSet(new TreeSet());
+
+    protected ServiceManager serviceManager;
+    protected Context context;
 
     /**
-     * Maintains the forest of <code>WebContinuation</code> trees.
-     * This set is used only for debugging purposes by
-     * {@link #displayAllContinuations()} method.
+     * How long does a continuation exist in memory since the last
+     * access? The time is in milliseconds, and the default is 1 hour.
      */
-    protected Set forest = Collections.synchronizedSet(new HashSet());
+    protected int defaultTimeToLive;
+    protected boolean bindContinuationsToSession;
 
     /**
      * Main continuations holder. Used unless continuations are stored in user
      * session.
      */
     protected WebContinuationsHolder continuationsHolder;
-    
-    /**
-     * Sorted set of <code>WebContinuation</code> instances, based on
-     * their expiration time. This is used by the background thread to
-     * invalidate continuations.
-     */
-    protected SortedSet expirations = Collections.synchronizedSortedSet(new TreeSet());
-
-    protected boolean bindContinuationsToSession;
-
-    protected ServiceManager serviceManager;
-    protected Context context;
-
-    protected long expirationCheckInterval;
-
 
     public ContinuationsManagerImpl() throws Exception {
         try {
@@ -122,7 +115,6 @@
             random = SecureRandom.getInstance("IBMSecureRandom");
         }
         random.setSeed(System.currentTimeMillis());
-        bytes = new byte[CONTINUATION_ID_LENGTH];
     }
 
     public void contextualize(Context context) throws ContextException {
@@ -136,14 +128,16 @@
     public void configure(Configuration config) {
         this.defaultTimeToLive = config.getAttributeAsInteger("time-to-live", (3600 * 1000));
         this.bindContinuationsToSession = config.getAttributeAsBoolean( "session-bound-continuations", false );
+        
         // create a global ContinuationsHolder if this the "session-bound-continuations" parameter is set to false
         if (!this.bindContinuationsToSession) {
             this.continuationsHolder = new WebContinuationsHolder();
         }
         
+        // create a thread that invalidates the continuations
         final Configuration expireConf = config.getChild("expirations-check");
         final long initialDelay = expireConf.getChild("offset", true).getValueAsLong(180000);
-        this.expirationCheckInterval = expireConf.getChild("period", true).getValueAsLong(180000);
+        final long interval = expireConf.getChild("period", true).getValueAsLong(180000);
         try {
             final RunnableManager runnableManager = (RunnableManager)serviceManager.lookup(RunnableManager.ROLE);
             runnableManager.execute( new Runnable() {
@@ -151,7 +145,7 @@
                     {
                         expireContinuations();
                     }
-                }, initialDelay, expirationCheckInterval);
+                }, initialDelay, interval);
             serviceManager.release(runnableManager);
         } catch (Exception e) {
             getLogger().warn("Could not enqueue continuations expiration task. " +
@@ -159,60 +153,28 @@
         }
     }
 
-    
     public WebContinuation createWebContinuation(Object kont,
                                                  WebContinuation parent,
                                                  int timeToLive,
                                                  String interpreterId, 
                                                  ContinuationsDisposer disposer) {
-        int ttl = (timeToLive == 0 ? defaultTimeToLive : timeToLive);
+        int ttl = timeToLive == 0 ? defaultTimeToLive : timeToLive;
 
         WebContinuation wk = generateContinuation(kont, parent, ttl, interpreterId, disposer);
 
-        if (parent == null) {
-            forest.add(wk);
-        } else {
-            handleParentContinuationExpiration(parent);
+        synchronized (this.expirations) {
+            if (parent != null) {
+                expirations.remove(parent);
+            }
+            expirations.add(wk);
         }
 
-        handleLeafContinuationExpiration(wk);
-
         if (getLogger().isDebugEnabled()) {
             getLogger().debug("WK: Created continuation " + wk.getId());
         }
 
         return wk;
     }
-    
-    /**
-     * When a new continuation is created in @link #createWebContinuation(Object, WebContinuation, int, String, ContinuationsDisposer),
-     * it is registered in the expiration set in order to be evaluated by the invalidation mechanism.
-     */
-    protected void handleLeafContinuationExpiration(WebContinuation wk) {
-        expirations.add(wk);
-    }
-
-    /**
-     * When a new continuation is created in @link #createWebContinuation(Object, WebContinuation, int, String, ContinuationsDisposer),
-     * its parent continuation is removed from the expiration set. This way only leaf continuations are part of
-     * the expiration set.
-     */
-    protected void handleParentContinuationExpiration(WebContinuation parent) {
-        if (parent.getChildren().size() < 2) {
-            expirations.remove(parent);
-        }
-    }    
-    
-    /**
-     * Get a list of all web continuations (data only)
-     */
-    public List getWebContinuationsDataBeanList() {
-        List beanList = new ArrayList();
-        for(Iterator it = this.forest.iterator(); it.hasNext();) {
-            beanList.add(new WebContinuationDataBean((WebContinuation) it.next()));
-        }
-        return beanList;
-    }
 
     public WebContinuation lookupWebContinuation(String id, String interpreterId) {
         WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(false);
@@ -231,52 +193,42 @@
         }
             
         if (!kont.interpreterMatches(interpreterId)) {
-            getLogger().error(
-                    "WK: Continuation (" + kont.getId()
-                            + ") lookup for wrong interpreter. Bound to: "
-                            + kont.getInterpreterId() + ", looked up for: "
-                            + interpreterId);
+            getLogger().error("WK: Continuation (" + kont.getId()
+                              + ") lookup for wrong interpreter. Bound to: "
+                              + kont.getInterpreterId() + ", looked up for: "
+                              + interpreterId);
             return null;
         }
-        
+
         // COCOON-2109: Sorting in the TreeSet happens on insert. So in order to re-sort the
         //              continuation has to be re-added.
-        expirations.remove(kont);
-        kont.updateLastAccessTime();
-        expirations.add(kont);
-        
+        synchronized (this.expirations) {
+            this.expirations.remove(kont);
+            kont.updateLastAccessTime();
+            this.expirations.add(kont);
+        }
+
         return kont;
     }
 
     /**
-     * Create <code>WebContinuation</code> and generate unique identifier for
-     * it. The identifier is generated using a cryptographically strong
+     * Create <code>WebContinuation</code> and generate unique identifier
+     * for it. The identifier is generated using a cryptographically strong
      * algorithm to prevent people to generate their own identifiers.
-     * 
-     * <p>
-     * It has the side effect of interning the continuation object in the
-     * <code>idToWebCont</code> hash table.
-     * 
-     * @param kont
-     *            an <code>Object</code> value representing continuation
-     * @param parent
-     *            value representing parent <code>WebContinuation</code>
-     * @param ttl
-     *            <code>WebContinuation</code> time to live
-     * @param interpreterId
-     *            id of interpreter invoking continuation creation
-     * @param disposer
-     *            <code>ContinuationsDisposer</code> instance to use for
-     *            cleanup of the continuation.
-     * @return the generated <code>WebContinuation</code> with unique
-     *         identifier
+     *
+     * @param kont an <code>Object</code> value representing continuation
+     * @param parent value representing parent <code>WebContinuation</code>
+     * @param ttl <code>WebContinuation</code> time to live
+     * @param interpreterId id of interpreter invoking continuation creation
+     * @param disposer <code>ContinuationsDisposer</code> instance to use for
+     * cleanup of the continuation.
+     * @return the generated <code>WebContinuation</code> with unique identifier
      */
     protected WebContinuation generateContinuation(Object kont,
-                                                 WebContinuation parent,
-                                                 int ttl,
-                                                 String interpreterId,
-                                                 ContinuationsDisposer disposer) {
-
+                                                   WebContinuation parent,
+                                                   int ttl,
+                                                   String interpreterId,
+                                                   ContinuationsDisposer disposer) {
         char[] result = new char[bytes.length * 2];
         WebContinuation wk;
         WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(true);
@@ -290,15 +242,16 @@
             }
 
             final String id = new String(result);
-            synchronized (continuationsHolder) {
+            synchronized (continuationsHolder.holder) {
                 if (!continuationsHolder.contains(id)) {
-                    if (this.bindContinuationsToSession)
+                    if (this.bindContinuationsToSession) {
                         wk = new HolderAwareWebContinuation(id, kont, parent,
                                                             ttl, interpreterId, disposer,
                                                             continuationsHolder);
-                    else
+                    } else {
                         wk = new WebContinuation(id, kont, parent, ttl,
                                                  interpreterId, disposer);
+                    }
                     continuationsHolder.addContinuation(wk);
                     break;
                 }
@@ -335,17 +288,14 @@
 
     /**
      * Detach this continuation from parent. This method removes
-     * continuation from {@link #forest} set, or, if it has parent,
-     * from parent's children collection.
-     *
+     * continuation from parent's children collection, if it has parent.
      * @param wk Continuation to detach from parent.
      */
-    protected void _detach(WebContinuation wk) {
+    private void _detach(WebContinuation wk) {
         WebContinuation parent = wk.getParentContinuation();
-        if (parent == null) {
-            forest.remove(wk);
-        } else 
+        if (parent != null) {
             wk.detachFromParent();
+        }
     }
 
     /**
@@ -368,13 +318,12 @@
      *
      * @param wk <code>WebContinuation</code> node
      */
-    protected void removeContinuation(WebContinuationsHolder continuationsHolder,
-            WebContinuation wk) {
+    protected void removeContinuation(WebContinuationsHolder continuationsHolder, WebContinuation wk) {
         if (wk.getChildren().size() != 0) {
             return;
         }
 
-        // remove access to this contination
+        // remove access to this continuation
         disposeContinuation(continuationsHolder, wk);
         _detach(wk);
 
@@ -391,42 +340,6 @@
     }
 
     /**
-     * Dump to Log file the current contents of
-     * the expirations <code>SortedSet</code>
-     */
-    protected void displayExpireSet() {
-        StringBuffer wkSet = new StringBuffer("\nWK; Expire set size: " + expirations.size());
-        Iterator i = expirations.iterator();
-        while (i.hasNext()) {
-            final WebContinuation wk = (WebContinuation) i.next();
-            final long lat = wk.getLastAccessTime() + wk.getTimeToLive();
-            wkSet.append("\nWK: ")
-                    .append(wk.getId())
-                    .append(" ExpireTime [");
-
-            if (lat < System.currentTimeMillis()) {
-                wkSet.append("Expired");
-            } else {
-                wkSet.append(lat);
-            }
-            wkSet.append("]");
-        }
-
-        getLogger().debug(wkSet.toString());
-    }
-
-    /**
-     * Dump to Log file all <code>WebContinuation</code>s
-     * in the system
-     */
-    public void displayAllContinuations() {
-        final Iterator i = forest.iterator();
-        while (i.hasNext()) {
-            getLogger().debug(i.next().toString());
-        }
-    }
-
-    /**
      * Remove all continuations which have already expired.
      */
     protected void expireContinuations() {
@@ -434,37 +347,35 @@
         if (getLogger().isDebugEnabled()) {
             now = System.currentTimeMillis();
 
-            /* Continuations before clean up:
-            getLogger().debug("WK: Forest before cleanup: " + forest.size());
+            /* Continuations before clean up: */
             displayAllContinuations();
             displayExpireSet();
-            */
         }
 
         // Clean up expired continuations
         int count = 0;
-        WebContinuation wk;
-        Iterator i = expirations.iterator();
-        while (i.hasNext() && ((wk = (WebContinuation) i.next()).hasExpired())) {
-            i.remove();
-            WebContinuationsHolder continuationsHolder;
-            if (wk instanceof HolderAwareWebContinuation)
-                continuationsHolder = ((HolderAwareWebContinuation) wk).getContinuationsHolder();
-            else
-                continuationsHolder = this.continuationsHolder;
-            removeContinuation(continuationsHolder, wk);
-            count++;
+        FilterIterator expirationIterator = new FilterIterator();
+        Predicate expirationPredicate = new ExpirationPredicate();
+        expirationIterator.setPredicate(expirationPredicate);
+        synchronized (this.expirations) {
+            expirationIterator.setIterator(this.expirations.iterator());
+            while (expirationIterator.hasNext()) {
+                WebContinuation wk = (WebContinuation) expirationIterator.next();
+                expirationIterator.remove();
+                WebContinuationsHolder continuationsHolder;
+                if (wk instanceof HolderAwareWebContinuation) {
+                    continuationsHolder = ((HolderAwareWebContinuation) wk).getContinuationsHolder();
+                } else {
+                    continuationsHolder = this.continuationsHolder;
+                }
+                removeContinuation(continuationsHolder, wk);
+                count++;
+            }
         }
 
         if (getLogger().isDebugEnabled()) {
             getLogger().debug("WK Cleaned up " + count + " continuations in " +
-                              (System.currentTimeMillis() - now));
-
-            /* Continuations after clean up:
-            getLogger().debug("WK: Forest after cleanup: " + forest.size());
-            displayAllContinuations();
-            displayExpireSet();
-            */
+                              (System.currentTimeMillis() - now) + " ms");
         }
     }
 
@@ -473,16 +384,10 @@
      * about session invalidation. Invalidates all continuations held by passed
      * continuationsHolder.
      */
-    protected void invalidateContinuations(
-            WebContinuationsHolder continuationsHolder) {
-        // TODO: this avoids ConcurrentModificationException, still this is not
-        // the best solution and should be changed
-        Object[] continuationIds = continuationsHolder.getContinuationIds()
-                .toArray();
-        
-        for (int i = 0; i < continuationIds.length; i++) {
-            WebContinuation wk = continuationsHolder.get(continuationIds[i]);
-            if (wk != null) {
+    protected void invalidateContinuations(WebContinuationsHolder continuationsHolder) {
+        synchronized (continuationsHolder.holder) {
+            for (Iterator iter = continuationsHolder.holder.values().iterator(); iter.hasNext();) {
+                WebContinuation wk = (WebContinuation) iter.next();
                 _detach(wk);
                 _invalidate(continuationsHolder, wk);
             }
@@ -518,18 +423,97 @@
             return holder;
 
         holder = new WebContinuationsHolder();
-        session.setAttribute(WebContinuationsHolder.CONTINUATIONS_HOLDER,
-                holder);
+        session.setAttribute(WebContinuationsHolder.CONTINUATIONS_HOLDER, holder);
         return holder;
     }
 
+    public Set getForest() {
+        Set rootWebContinuations = new HashSet();
+        // identify the root continuations, once done no more need to lock
+        synchronized (this.expirations) {
+            for (Iterator iter = this.expirations.iterator(); iter.hasNext();) {
+                WebContinuation webContinuation = (WebContinuation) iter.next();
+                while (webContinuation.getParentContinuation() != null) {
+                    webContinuation = webContinuation.getParentContinuation();
+                }
+                rootWebContinuations.add(webContinuation);
+            }
+        }
+        
+        Set clonedRootWebContinuations = new HashSet();
+        for (Iterator iter = rootWebContinuations.iterator(); iter.hasNext();) {
+            WebContinuation rootContinuation = (WebContinuation) iter.next();
+            clonedRootWebContinuations.add(rootContinuation.clone());
+        }
+        return clonedRootWebContinuations;
+    }
+
+    /**
+     * Get a list of all web continuations (data only)
+     * 
+     * @deprecated
+     */
+    public List getWebContinuationsDataBeanList() {
+        if (Deprecation.logger.isWarnEnabled()) {
+            Deprecation.logger.warn("ContinuationsManager.getWebContinuationsDataBeanList()"
+                    + " is deprecated and should be replaced with getForest().");
+        }
+        List beanList = new ArrayList();
+        for (Iterator it = getForest().iterator(); it.hasNext();) {
+            beanList.add(new WebContinuationDataBean((WebContinuation) it.next()));
+        }
+        return beanList;
+    }
+
+    /**
+     * Dump to Log file the current contents of
+     * the expirations <code>SortedSet</code>
+     */
+    protected void displayExpireSet() {
+        StringBuffer wkSet = new StringBuffer("\nWK; Expire set size: ");
+        
+        synchronized (this.expirations) {
+            wkSet.append(this.expirations.size());
+            for (Iterator i = this.expirations.iterator(); i.hasNext();) {
+                final WebContinuation wk = (WebContinuation) i.next();
+                wkSet.append("\nWK: ").append(wk.getId()).append(" ExpireTime [");
+                if (wk.hasExpired()) {
+                    wkSet.append("Expired");
+                } else {
+                    wkSet.append(wk.getLastAccessTime() + wk.getTimeToLive());
+                }
+                wkSet.append("]");
+            }
+        }
+        getLogger().debug(wkSet.toString());
+    }
+
+    /**
+     * Dump to Log file all <code>WebContinuation</code>s
+     * in the system.
+     * 
+     * This method will be changed to be an internal method solely for debugging
+     * purposes just like {@link #displayExpireSet()}.
+     */
+    public void displayAllContinuations() {
+        if (getLogger().isDebugEnabled()) {
+            Set forest = getForest();
+            getLogger().debug("WK: Forest size: " + forest.size());
+            for (Iterator iter = forest.iterator(); iter.hasNext();) {
+                getLogger().debug(iter.next().toString());
+            }
+        }
+    }
+
     /**
      * A holder for WebContinuations. When bound to session notifies the
      * continuations manager of session invalidation.
+     * 
+     * For thread-safe access you have to synchronize on the Map {@link #holder}!
      */
-    public class WebContinuationsHolder implements HttpSessionBindingListener {
-        private final static String CONTINUATIONS_HOLDER = 
-                                       "o.a.c.c.f.SCMI.WebContinuationsHolder";
+    protected class WebContinuationsHolder implements HttpSessionBindingListener {
+        
+        private final static String CONTINUATIONS_HOLDER = "o.a.c.c.f.SCMI.WebContinuationsHolder";
 
         private Map holder = Collections.synchronizedMap(new HashMap());
 
@@ -563,6 +547,7 @@
         public void valueUnbound(HttpSessionBindingEvent event) {
             invalidateContinuations(this);
         }
+
     }
 
     /**
@@ -570,7 +555,8 @@
      * holder. This information is needed to cleanup a proper holder after
      * continuation's expiration time.
      */
-    protected class HolderAwareWebContinuation extends WebContinuation {
+    protected static class HolderAwareWebContinuation extends WebContinuation {
+
         private WebContinuationsHolder continuationsHolder;
 
         public HolderAwareWebContinuation(String id,
@@ -588,10 +574,14 @@
             return continuationsHolder;
         }
 
-        //retain comparation logic from parent
-        public int compareTo(Object other) {
-            return super.compareTo(other);
+    }
+
+    protected static class ExpirationPredicate implements Predicate {
+
+        public boolean evaluate(final Object obj) {
+            return ((WebContinuation)obj).hasExpired();
         }
+
     }
 
 }