[jboss-cvs] JBossAS SVN: r80003 - in trunk: tomcat/src/main/org/jboss/web/tomcat/service/session and 1 other directory.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Thu Oct 23 14:55:57 EDT 2008


Author: bstansberry at jboss.com
Date: 2008-10-23 14:55:56 -0400 (Thu, 23 Oct 2008)
New Revision: 80003

Added:
   trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockDistributedCacheManager.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredManager.java
Removed:
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheClusteredSession.java
Modified:
   trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockJBossManager.java
   trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockSession.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/AttributeBasedClusteredSession.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredSession.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/FieldBasedClusteredSession.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/LocalStrings.properties
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/LocalStrings_ja.properties
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/SessionBasedClusteredSession.java
Log:
[JBAS-6109] ClusteredSession no longer subclasses StandardSession

Added: trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockDistributedCacheManager.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockDistributedCacheManager.java	                        (rev 0)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockDistributedCacheManager.java	2008-10-23 18:55:56 UTC (rev 80003)
@@ -0,0 +1,178 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.test.cluster.web.jvmroute;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.jboss.web.tomcat.service.session.distributedcache.spi.BatchingManager;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSession;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.FieldBasedDistributedCacheManager;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.LocalDistributableSessionManager;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class MockDistributedCacheManager implements FieldBasedDistributedCacheManager
+{
+   public static final MockDistributedCacheManager INSTANCE = new MockDistributedCacheManager();
+   
+
+   public void evictSession(String realId)
+   {
+      // no-op
+   }
+
+   public void evictSession(String realId, String dataOwner)
+   {
+      // no-op
+   }
+
+   public Object getAttribute(String realId, String key)
+   {
+      return null;
+   }
+
+   public Set getAttributeKeys(String realId)
+   {
+      return Collections.EMPTY_SET;
+   }
+
+   public Map getAttributes(String realId)
+   {
+      return Collections.EMPTY_MAP;
+   }
+
+   public BatchingManager getBatchingManager()
+   {
+      return null;
+   }
+
+   public Map getSessionData(String realId, String dataOwner)
+   {
+      return Collections.EMPTY_MAP;
+   }
+
+   public Map<String, String> getSessionIds()
+   {
+      return Collections.EMPTY_MAP;
+   }
+
+   public boolean isPassivationEnabled()
+   {
+      return false;
+   }
+
+   public <T extends DistributableSession> T loadSession(String realId, T toLoad)
+   {
+      return null;
+   }
+
+   public void putAttribute(String realId, Map map)
+   {
+      // no-op
+   }
+
+   public void putAttribute(String realId, String key, Object value)
+   {
+      // no-op
+   }
+
+   public void putSession(String realId, DistributableSession session)
+   {
+      // no-op
+   }
+
+   public Object removeAttribute(String realId, String key)
+   {
+      return null;
+   }
+
+   public void removeAttributes(String realId)
+   {
+      // no-op
+   }
+
+   public void removeAttributesLocal(String realId)
+   {
+      // no-op
+   }
+
+   public void removeSession(String realId, boolean removeRegion)
+   {
+      // no-op
+   }
+
+   public void removeSessionLocal(String realId, boolean removeRegion)
+   {
+      // no-op
+   }
+
+   public void removeSessionLocal(String realId, String dataOwner)
+   {
+      // no-op
+   }
+
+   public void start(ClassLoader tcl, LocalDistributableSessionManager manager)
+   {
+      // no-op
+   }
+
+   public void stop()
+   {
+      // no-op
+   }
+
+   public Object getPojo(String realId, String key)
+   {
+      return null;
+   }
+
+   public Set getPojoKeys(String realId)
+   {
+      return Collections.EMPTY_SET;
+   }
+
+   public Object removePojo(String realId, String key)
+   {
+      return null;
+   }
+
+   public void removePojoLocal(String realId, String key)
+   {
+      // no-op
+   }
+
+   public void removePojosLocal(String realId)
+   {
+      // no-op
+   }
+
+   public Object setPojo(String realId, String key, Object pojo, DistributableSession session)
+   {
+      return null;
+   }
+
+}

Modified: trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockJBossManager.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockJBossManager.java	2008-10-23 18:15:21 UTC (rev 80002)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockJBossManager.java	2008-10-23 18:55:56 UTC (rev 80003)
@@ -31,14 +31,18 @@
 import org.apache.catalina.Session;
 import org.jboss.metadata.web.jboss.JBossWebMetaData;
 import org.jboss.metadata.web.jboss.ReplicationTrigger;
-import org.jboss.web.tomcat.service.session.AbstractJBossManager;
+import org.jboss.web.tomcat.service.session.ClusteredManager;
 import org.jboss.web.tomcat.service.session.distributedcache.spi.ClusteringNotSupportedException;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.FieldBasedDistributedCacheManager;
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationPolicy;
+import org.jboss.web.tomcat.service.session.notification.LegacyClusteredSessionNotificationPolicy;
 
 /**
  * @author Brian Stansberry
  *
  */
-public class MockJBossManager implements AbstractJBossManager
+public class MockJBossManager implements ClusteredManager
 {
    private String jvmRoute = null;
    private String newCookieIdSession = null;
@@ -248,4 +252,32 @@
    {
    }
 
+   public DistributedCacheManager getDistributedCacheManager()
+   {
+      return MockDistributedCacheManager.INSTANCE;
+   }
+
+   public FieldBasedDistributedCacheManager getFieldBasedDistributedCacheManager()
+   {
+      return MockDistributedCacheManager.INSTANCE;
+   }
+
+   public int getMaxUnreplicatedInterval()
+   {
+      return -1;
+   }
+
+   public ClusteredSessionNotificationPolicy getNotificationPolicy()
+   {
+      // TODO Auto-generated method stub
+      return new LegacyClusteredSessionNotificationPolicy();
+   }
+
+   public boolean getUseJK()
+   {
+      return true;
+   }
+   
+   
+
 }

Modified: trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockSession.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockSession.java	2008-10-23 18:15:21 UTC (rev 80002)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockSession.java	2008-10-23 18:55:56 UTC (rev 80003)
@@ -22,13 +22,10 @@
 
 package org.jboss.test.cluster.web.jvmroute;
 
-import java.util.Map;
-
 import javax.servlet.http.HttpSession;
 
 import org.apache.catalina.session.StandardSessionFacade;
-import org.jboss.metadata.web.jboss.ReplicationTrigger;
-import org.jboss.web.tomcat.service.session.AbstractJBossManager;
+import org.jboss.web.tomcat.service.session.ClusteredManager;
 import org.jboss.web.tomcat.service.session.ClusteredSession;
 import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause;
 
@@ -48,11 +45,11 @@
     */
    public MockSession(MockJBossManager manager)
    {
-      super(manager, ReplicationTrigger.SET_AND_NON_PRIMITIVE_GET, true);
+      super(manager);
    }
 
    @Override
-   public void tellNew()
+   public void tellNew(ClusteredSessionNotificationCause cause)
    {
       // no-op
    }
@@ -69,29 +66,29 @@
    {
       return new StandardSessionFacade(this);
    }
-
-   // Inherited abstract methods
    
    @Override
-   protected Object getJBossInternalAttribute(String name)
+   public String getInfo()
    {
-      return null;
+      return info;
    }
 
+   // Inherited abstract methods
+   
    @Override
-   protected Map getJBossInternalAttributes()
+   protected Object getAttributeInternal(String name)
    {
       return null;
    }
 
    @Override
-   public void initAfterLoad(AbstractJBossManager manager, ClusteredSessionNotificationCause cause)
+   public void initAfterLoad(ClusteredManager manager, ClusteredSessionNotificationCause cause)
    {
       
    }
 
    @Override
-   public void processSessionRepl()
+   public void processSessionReplication()
    {
    }
 
@@ -103,19 +100,24 @@
    @Override
    public void removeMyselfLocal()
    {
+   }  
+
+   @Override
+   protected void populateAttributes()
+   {
    }
 
    @Override
-   protected Object setJBossInternalAttribute(String name, Object value)
+   protected Object setAttributeInternal(String name, Object value)
    {
       return null;
    }
 
    @Override
-   protected Object removeJBossInternalAttribute(String name, boolean localCall, boolean localOnly)
+   protected Object removeAttributeInternal(String name, boolean localCall, boolean localOnly)
    {
       return null;
-   }
+   }   
    
    
 }

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/AttributeBasedClusteredSession.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/AttributeBasedClusteredSession.java	2008-10-23 18:15:21 UTC (rev 80002)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/AttributeBasedClusteredSession.java	2008-10-23 18:55:56 UTC (rev 80003)
@@ -21,25 +21,15 @@
 */
 package org.jboss.web.tomcat.service.session;
 
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
 
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager;
+
 /**
- * Implementation of a clustered session for the JBossCacheManager. The replication granularity
+ * Implementation of a clustered session where the replication granularity
  * level is attribute based; that is, we replicate only the dirty attributes.
- * We use JBossCache for our internal, deplicated data store.
- * The internal structure is like in JBossCache:
- * <pre>
- * /JSESSION
- *    /hostname
- *       /web_app_path    (path + session id is unique)
- *           /id   Map(id, session)
- *                    (VERSION_KEY, version)  // Used for version tracking. version is an Integer.
- *              /ATTRIBUTE    Map(attr_key, value)
- * </pre>
  * <p/>
  * Note that the isolation level of the cache dictates the
  * concurrency behavior. Also note that session and its associated attribtues are stored in different nodes.
@@ -52,7 +42,7 @@
  * @version $Revision: 56542 $
  */
 class AttributeBasedClusteredSession
-   extends JBossCacheClusteredSession
+   extends ClusteredSession<DistributedCacheManager>
 {
    static final long serialVersionUID = -5625209785550936713L;
    /**
@@ -61,22 +51,27 @@
    protected static final String info = "AttributeBasedClusteredSession/1.0";
 
    // Transient map to store attr changes for replication.
-   private transient Map<Object, Object> attrModifiedMap_ = new HashMap<Object, Object>();
+   private transient Map<String, Object> attrModifiedMap_ = new HashMap<String, Object>();
    // Note that the removed attr is intentionally stored in a map 
    // instead of a Set so it is faster to lookup and remove.
-   private transient Map attrRemovedMap_ = new HashMap();
-   private static final int REMOVE = 0;   // Used to track attribute changes
-   private static final int MODIFY = 1;
-   // TODO why isn't the superclass field sufficient?
-   private transient Map attributes_ = Collections.synchronizedMap(new HashMap());
+   private transient Map<String, Object> attrRemovedMap_ = new HashMap<String, Object>();
 
-   public AttributeBasedClusteredSession(JBossCacheManager manager)
+   // ------------------------------------------------------------ Constructors
+   
+
+   public AttributeBasedClusteredSession(ClusteredManager manager)
    {
       super(manager);
    }
 
+   
    // ----------------------------------------------- Overridden Public Methods
 
+   @Override
+   public String getInfo()
+   {
+      return (info);
+   }
 
    /**
     * Override the superclass to additionally reset this class' fields.
@@ -86,72 +81,22 @@
     * thorough.
     * </p>
     */
+   @Override
    public void recycle()
    {
       super.recycle();
 
-      attributes_.clear();
       clearAttrChangedMaps();
    }
 
-   /**
-    * Return a string representation of this object.
-    */
-   public String toString()
-   {
-
-      StringBuffer sb = new StringBuffer();
-      sb.append("AttributeBasedClusteredSession[");
-      sb.append(super.toString());
-      sb.append("]");
-      return (sb.toString());
-
-   }
-
-   /**
-    * Overrides the superclass version to read in the attributes.
-    */
-   protected synchronized void replicateAttributes()
-   {
-      // Go thru the attribute change list
-      
-      if (isSessionAttributeMapDirty())
-      {
-         // Go thru the modified attr list first
-         int modCount = attrModifiedMap_.size();
-         if (modCount == 1)
-         {
-            for (Map.Entry entry : attrModifiedMap_.entrySet())
-            {
-               proxy_.putAttribute(realId, (String) entry.getKey(), entry.getValue());
-            }
-         }
-         else if (modCount > 0)
-         {
-            // It's more efficient to write a map than 2 method calls,
-            // plus it reduces the number of CacheListener notifications
-            proxy_.putAttribute(realId, attrModifiedMap_);
-         }
-         
-         // Go thru the remove attr list
-         if (attrRemovedMap_.size() > 0)
-         {         
-            for (Iterator it = attrRemovedMap_.keySet().iterator(); it.hasNext(); )
-            {
-               proxy_.removeAttribute(realId, (String) it.next());
-            }
-         }
-         
-         clearAttrChangedMaps();
-      }
-   }
-
+   @Override
    public void removeMyself()
    {
       // This is a shortcut to remove session and it's child attributes.
-      proxy_.removeSession(realId, false);
+      getDistributedCacheManager().removeSession(getRealId(), false);
    }
 
+   @Override
    public void removeMyselfLocal()
    {
       // Need to evict attribute first before session to clean up everything.
@@ -159,101 +104,132 @@
       // removeAttributesLocal call here in order to evict the ATTRIBUTE node.  
       // Otherwise empty nodes for the session root and child ATTRIBUTE will 
       // remain in the tree and screw up our list of session names.
-      proxy_.removeAttributesLocal(realId);
-      proxy_.removeSessionLocal(realId, false);
+      getDistributedCacheManager().removeAttributesLocal(getRealId());
+      getDistributedCacheManager().removeSessionLocal(getRealId(), false);
    }
 
-   // ------------------------------------------------ JBoss internal abstract method
+   // -------------------------------------------- Overridden Protected Methods
 
    /**
     * Populate the attributes stored in the distributed store to local transient ones.
     */
+   @Override
    protected void populateAttributes()
    {
-      Map map = proxy_.getAttributes(realId);
+      Map map = getDistributedCacheManager().getAttributes(getRealId());
+      Map<String, Object> attributes = getAttributesInternal();
       
       // Preserve any local attributes that were excluded from replication
-      Map excluded = removeExcludedAttributes(attributes_);
+      Map excluded = removeExcludedAttributes(attributes);
       if (excluded != null)
          map.putAll(excluded);
       
-      attributes_ = Collections.synchronizedMap(map);
+      attributes.clear();
+      attributes.putAll(map);
       attrModifiedMap_.clear();
       attrRemovedMap_.clear();
    }
 
-   protected Object getJBossInternalAttribute(String name)
+   @Override
+   protected Object getAttributeInternal(String name)
    {
-      Object result = attributes_.get(name);
+      Object result = getAttributesInternal().get(name);
 
       // Do dirty check even if result is null, as w/ SET_AND_GET null
       // still makes us dirty (ensures timely replication w/o using ACCESS)
       if (isGetDirty(result) && !replicationExcludes.contains(name))
       {
-         attributeChanged(name, result, MODIFY);
+         attributeChanged(name, result, false);
       }
 
       return result;
    }
 
-   protected Object removeJBossInternalAttribute(String name, 
+   @Override
+   protected Object removeAttributeInternal(String name, 
                                                  boolean localCall, 
                                                  boolean localOnly)
    {
-      Object result = attributes_.remove(name);
+      Object result = getAttributesInternal().remove(name);
       if (localCall && !replicationExcludes.contains(name))
-         attributeChanged(name, result, REMOVE);
+         attributeChanged(name, result, true);
       return result;
    }
 
-   protected Map getJBossInternalAttributes()
+   @Override
+   protected Object setAttributeInternal(String key, Object value)
    {
-      return attributes_;
+      Object old = getAttributesInternal().put(key, value);
+      if (!replicationExcludes.contains(key))
+         attributeChanged(key, value, false);
+      return old;
    }
 
-   protected Set getJBossInternalKeys()
-   {
-      return attributes_.keySet();
-   }
-
    /**
-    * Method inherited from Tomcat. Return zero-length based string if not found.
+    * Overrides the superclass version to read in the attributes.
     */
-   protected String[] keys()
+   @Override
+   protected synchronized void replicateAttributes()
    {
-      return ((String[]) getJBossInternalKeys().toArray(EMPTY_ARRAY));
+      // Go thru the attribute change list
+      String myRealId = getRealId();
+      if (isSessionAttributeMapDirty())
+      {
+         DistributedCacheManager distributedCacheManager = getDistributedCacheManager();
+         
+         // Go thru the modified attr list first
+         int modCount = attrModifiedMap_.size();
+         if (modCount == 1)
+         {
+            for (Map.Entry entry : attrModifiedMap_.entrySet())
+            {
+               distributedCacheManager.putAttribute(myRealId, (String) entry.getKey(), entry.getValue());
+            }
+         }
+         else if (modCount > 0)
+         {
+            // It's more efficient to write a map than 2 method calls,
+            // plus it reduces the number of CacheListener notifications
+            distributedCacheManager.putAttribute(myRealId, attrModifiedMap_);
+         }
+         
+         // Go thru the remove attr list
+         if (attrRemovedMap_.size() > 0)
+         {         
+            for (Iterator it = attrRemovedMap_.keySet().iterator(); it.hasNext(); )
+            {
+               distributedCacheManager.removeAttribute(myRealId, (String) it.next());
+            }
+         }
+         
+         clearAttrChangedMaps();
+      }
    }
 
-   protected Object setJBossInternalAttribute(String key, Object value)
-   {
-      Object old = attributes_.put(key, value);
-      if (!replicationExcludes.contains(key))
-         attributeChanged(key, value, MODIFY);
-      return old;
-   }
+   // ------------------------------------------------------- Private Methods
 
-   protected synchronized void attributeChanged(Object key, Object value, int op)
+   private synchronized void attributeChanged(String key, Object value, boolean removal)
    {
-      if (op == MODIFY)
+      if (removal)
       {
-         if (attrRemovedMap_.containsKey(key))
+         if (attrModifiedMap_.containsKey(key))
          {
-            attrRemovedMap_.remove(key);
+            attrModifiedMap_.remove(key);
          }
-         attrModifiedMap_.put(key, value);
+         attrRemovedMap_.put(key, value);
       }
-      else if (op == REMOVE)
+      else
       {
-         if (attrModifiedMap_.containsKey(key))
+         if (attrRemovedMap_.containsKey(key))
          {
-            attrModifiedMap_.remove(key);
+            attrRemovedMap_.remove(key);
          }
-         attrRemovedMap_.put(key, value);
+         attrModifiedMap_.put(key, value);
       }
       sessionAttributesDirty();
    }
 
-   protected synchronized void clearAttrChangedMaps()
+   private synchronized void clearAttrChangedMaps()
    {
       attrRemovedMap_.clear();
       attrModifiedMap_.clear();

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredManager.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredManager.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredManager.java	2008-10-23 18:55:56 UTC (rev 80003)
@@ -0,0 +1,89 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.session;
+
+import org.jboss.metadata.web.jboss.ReplicationTrigger;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager;
+import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationPolicy;
+
+/**
+ * View of a Manager from a ClusteredSession.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public interface ClusteredManager extends AbstractJBossManager
+{
+   
+   /**
+    * Get the maximum interval between requests, in seconds, after which a
+    * request will trigger replication of the session's metadata regardless
+    * of whether the request has otherwise made the session dirty. Such 
+    * replication ensures that other nodes in the cluster are aware of a 
+    * relatively recent value for the session's timestamp and won't incorrectly
+    * expire an unreplicated session upon failover.
+    * <p/>
+    * Default value is <code>-1</code>.
+    * <p/>
+    * The cost of the metadata replication depends on the configured
+    * {@link #setReplicationGranularityString(String) replication granularity}.
+    * With <code>SESSION</code>, the sesssion's attribute map is replicated 
+    * along with the metadata, so it can be fairly costly.  With other 
+    * granularities, the metadata object is replicated separately from the
+    * attributes and only contains a String, and a few longs, ints and booleans.
+    * 
+    * @return the maximum interval since last replication after which a request
+    *         will trigger session metadata replication. A value of 
+    *         <code>0</code> means replicate metadata on every request; a value 
+    *         of <code>-1</code> means never replicate metadata unless the 
+    *         session is otherwise dirty.
+    */
+   int getMaxUnreplicatedInterval();
+   
+   /**
+    * Gets the policy for determining whether the servlet spec notifications related
+    * to session events are allowed to be emitted on the local cluster node.
+    */
+   ClusteredSessionNotificationPolicy getNotificationPolicy();
+   
+   /**
+    * Gets the policy controlling whether session attribute reads and writes
+    * mark the session/attribute as needing replication.
+    * 
+    * @return SET, SET_AND_GET, SET_AND_NON_PRIMITIVE_GET or <code>null</code> 
+    *         if this has not yet been configured.
+    */
+   ReplicationTrigger getReplicationTrigger();
+   
+   /**
+    * Gets whether JK is being used and special handling of a jvmRoute
+    * portion of session ids is needed.
+    */
+   boolean getUseJK();
+   
+   /**
+    * Gets the <code>DistributedCacheManager</code> through which we interact
+    * with the distributed cache.
+    */
+   DistributedCacheManager getDistributedCacheManager();
+}

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredSession.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredSession.java	2008-10-23 18:15:21 UTC (rev 80002)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredSession.java	2008-10-23 18:55:56 UTC (rev 80003)
@@ -19,18 +19,45 @@
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
+
+/*
+ * 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.jboss.web.tomcat.service.session;
 
 import java.beans.PropertyChangeSupport;
 import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.security.AccessController;
 import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
 import javax.servlet.http.HttpSessionActivationListener;
 import javax.servlet.http.HttpSessionAttributeListener;
 import javax.servlet.http.HttpSessionBindingEvent;
@@ -40,15 +67,21 @@
 
 import org.apache.catalina.Context;
 import org.apache.catalina.Globals;
+import org.apache.catalina.Manager;
 import org.apache.catalina.Session;
+import org.apache.catalina.SessionEvent;
+import org.apache.catalina.SessionListener;
+import org.apache.catalina.security.SecurityUtil;
 import org.apache.catalina.session.StandardSession;
+import org.apache.catalina.session.StandardSessionFacade;
 import org.apache.catalina.util.Enumerator;
 import org.apache.catalina.util.StringManager;
 import org.jboss.logging.Logger;
 import org.jboss.metadata.web.jboss.ReplicationTrigger;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSession;
 import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionMetadata;
 import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionTimestamp;
-
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager;
 import org.jboss.web.tomcat.service.session.notification.ClusteredSessionManagementStatus;
 import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause;
 import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationPolicy;
@@ -56,20 +89,24 @@
 
 /**
  * Abstract base class for session clustering based on StandardSession. Different session
- * replication strategy can be implemented such as session- or attribute-based ones.
+ * replication strategies can be implemented by subclasses.
  *
  * @author Ben Wang
  * @author Brian Stansberry
  * 
  * @version $Revision: 58585 $
  */
-public abstract class ClusteredSession
-   extends StandardSession
+public abstract class ClusteredSession<D extends DistributedCacheManager>
+   implements HttpSession, Session, DistributableSession
 {
+   // --------------------------------------------------------------- Constants
+   
    private static final long serialVersionUID = -758573655613558722L;
-   protected static Logger log = Logger.getLogger(ClusteredSession.class);
 
-   // ----------------------------------------------------- Instance Variables
+   protected static final boolean ACTIVITY_CHECK = 
+      Globals.STRICT_SERVLET_COMPLIANCE
+      || Boolean.valueOf(System.getProperty("org.apache.catalina.session.StandardSession.ACTIVITY_CHECK", "false")).booleanValue();
+   
    /**
     * Descriptive information describing this Session implementation.
     */
@@ -78,9 +115,7 @@
    /**
     * Set of attribute names which are not allowed to be replicated/persisted.
     */
-   protected static final String[] excludedAttributes = {
-       Globals.SUBJECT_ATTR
-   };
+   protected static final String[] excludedAttributes = { Globals.SUBJECT_ATTR };
    
    /**
     * Set containing all members of {@link #excludedAttributes}.
@@ -95,57 +130,227 @@
       }
       replicationExcludes = Collections.unmodifiableSet(set);
    }
+
+
+   /**
+    * The method signature for the <code>fireContainerEvent</code> method.
+    */
+   protected static final Class containerEventTypes[] = { String.class, Object.class };
    
-   protected ReplicationTrigger invalidationPolicy;
+   protected static final Logger log = Logger.getLogger(ClusteredSession.class);
 
    /**
+    * The dummy HTTP session context used for servlet spec compliance.
+    */
+   @SuppressWarnings("deprecation")
+   protected static javax.servlet.http.HttpSessionContext sessionContext = new javax.servlet.http.HttpSessionContext()
+   {
+      private final Map empty = Collections.EMPTY_MAP;
+
+      public Enumeration getIds()
+      {
+         return new Enumerator(empty);
+      }
+
+      public HttpSession getSession(String sessionId)
+      {
+         return null;
+      }      
+   };
+
+   /**
+    * The string manager for this package.
+    */
+   protected static final StringManager sm =
+      StringManager.getManager(ClusteredSession.class.getPackage().getName());
+
+   // ----------------------------------------------------- Instance Variables
+
+   /**
+    * The collection of user data attributes associated with this Session.
+    */
+   private Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();
+
+
+   /**
+    * The authentication type used to authenticate our cached Principal,
+    * if any.  NOTE:  This value is not included in the serialized
+    * version of this object.
+    */
+   private transient String authType = null;
+
+
+   /**
+    * The <code>java.lang.Method</code> for the
+    * <code>fireContainerEvent()</code> method of the
+    * <code>org.apache.catalina.core.StandardContext</code> method,
+    * if our Context implementation is of this class.  This value is
+    * computed dynamically the first time it is needed, or after
+    * a session reload (since it is declared transient).
+    */
+   private transient Method containerEventMethod = null;
+
+
+   /**
+    * The time this session was created, in milliseconds since midnight,
+    * January 1, 1970 GMT.
+    */
+   private long creationTime = 0L;
+
+
+   /**
+    * We are currently processing a session expiration, so bypass
+    * certain IllegalStateException tests.  NOTE:  This value is not
+    * included in the serialized version of this object.
+    */
+   private transient boolean expiring = false;
+
+
+   /**
+    * The facade associated with this session.  NOTE:  This value is not
+    * included in the serialized version of this object.
+    */
+   private transient StandardSessionFacade facade = null;
+
+
+   /**
+    * The session identifier of this Session.
+    */
+   private String id = null;
+
+
+   /**
+    * The last accessed time for this Session.
+    */
+   private long lastAccessedTime = creationTime;
+
+
+   /**
+    * The session event listeners for this Session.
+    */
+   private transient ArrayList listeners = new ArrayList();
+
+
+   /**
+    * The Manager with which this Session is associated.
+    */
+   private transient ClusteredManager manager = null;
+   
+   /**
+    * Our proxy to the distributed cache.
+    */
+   private transient D distributedCacheManager;
+
+   /**
+    * The maximum time interval, in seconds, between client requests before
+    * the servlet container may invalidate this session.  A negative time
+    * indicates that the session should never time out.
+    */
+   private int maxInactiveInterval = -1;
+
+
+   /**
+    * Flag indicating whether this session is new or not.
+    */
+   private boolean isNew = false;
+
+
+   /**
+    * Flag indicating whether this session is valid or not.
+    */
+   private boolean isValid = false;
+
+   
+   /**
+    * Internal notes associated with this session by Catalina components
+    * and event listeners.  <b>IMPLEMENTATION NOTE:</b> This object is
+    * <em>not</em> saved and restored across session serializations!
+    */
+   private transient Map notes = new Hashtable();
+
+
+   /**
+    * The authenticated Principal associated with this session, if any.
+    * <b>IMPLEMENTATION NOTE:</b>  This object is <i>not</i> saved and
+    * restored across session serializations!
+    */
+   private transient Principal principal = null;
+
+
+   /**
+    * The property change support for this component.  NOTE:  This value
+    * is not included in the serialized version of this object.
+    */
+   private transient PropertyChangeSupport support =
+       new PropertyChangeSupport(this);
+
+
+   /**
+    * The current accessed time for this session.
+    */
+   private long thisAccessedTime = creationTime;
+
+
+   /**
+    * The access count for this session.
+    */
+   private transient AtomicInteger accessCount = null;
+   
+   /** 
+    * Policy controlling whether reading/writing attributes requires
+    * replication. 
+    */
+   private ReplicationTrigger invalidationPolicy;
+
+   
+   /**
     * If true, means the local in-memory session data contains metadata
     * changes that have not been published to the distributed cache. 
     */
-   protected transient boolean sessionMetadataDirty;
+   private transient boolean sessionMetadataDirty;
    
    /**
     * If true, means the local in-memory session data contains attribute
     * changes that have not been published to the distributed cache. 
     */
-   protected transient boolean sessionAttributesDirty;
+   private transient boolean sessionAttributesDirty;
    
    /** 
     * Object wrapping thisAccessedTime. Create once and mutate so
     * we can store it in JBoss Cache w/o concern that a transaction
     * rollback will revert the cached ref to an older object.
     */
-   protected transient DistributableSessionTimestamp timestamp = new DistributableSessionTimestamp();
+   private transient DistributableSessionTimestamp timestamp = new DistributableSessionTimestamp();
    
    /** 
     * Object wrapping other metadata for this session. Create once and mutate so
     * we can store it in JBoss Cache w/o concern that a transaction
     * rollback will revert the cached ref to an older object.
     */
-   protected transient DistributableSessionMetadata metadata = new DistributableSessionMetadata();
+   private transient DistributableSessionMetadata metadata = new DistributableSessionMetadata();
    
    /**
     * The last version that was passed to {@link #setDistributedVersion} or
     * <code>0</code> if <code>setIsOutdated(false)</code> was subsequently called.
     */
-   protected transient int outdatedVersion;
+   private transient int outdatedVersion;
    
    /**
     * The last time {@link #setIsOutdated  setIsOutdated(true)} was called or
     * <code>0</code> if <code>setIsOutdated(false)</code> was subsequently called.
     */
-   protected transient long outdatedTime;
+   private transient long outdatedTime;
 
    /**
     * Version number to track cache invalidation. If any new version number is 
     * greater than this one, it means the data it holds is newer than this one.
     */
-   protected int version;
+   private final AtomicInteger version = new AtomicInteger(0);
 
    /**
     * The session's id with any jvmRoute removed.
     */
-   protected transient String realId;
+   private transient String realId;
    
    /**
     * Whether JK is being used, in which case our realId will
@@ -156,318 +361,456 @@
    /**
     * Timestamp when we were last replicated.
     */
-   protected transient long lastReplicated;
+   private transient long lastReplicated;
    
    /**
     * Maximum number of milliseconds this session
     * should be allowed to go unreplicated if access to the
     * session doesn't mark it as dirty. 
     */
-   protected transient long maxUnreplicatedInterval;
+   private transient long maxUnreplicatedInterval;
    
    /** True if maxUnreplicatedInterval is 0 or less than maxInactiveInterval */
-   protected transient boolean alwaysReplicateTimestamp = true;
+   private transient boolean alwaysReplicateTimestamp = true;
    
    /**
     * Whether any of this session's attributes implement
     * HttpSessionActivationListener.
     */
-   protected transient Boolean hasActivationListener;
+   private transient Boolean hasActivationListener;
    
    /**
     * Has this session only been accessed once?
     */
-   protected transient boolean firstAccess;
+   private transient boolean firstAccess;
    
    /**
     * Policy that drives whether we issue servlet spec notifications.
     */
-   protected transient ClusteredSessionNotificationPolicy notificationPolicy;
+   private transient ClusteredSessionNotificationPolicy notificationPolicy;
    
-   protected transient ClusteredSessionManagementStatus clusterStatus;
+   private transient ClusteredSessionManagementStatus clusterStatus;
    
    /** True if a call to activate() is needed to offset a preceding passivate() call */
-   protected transient boolean needsPostReplicateActivation;
+   private transient boolean needsPostReplicateActivation;
+   
+   
+   // ------------------------------------------------------------ Constructors
 
    /**
-    * The string manager for this package.
+    * Creates a new ClusteredSession.
+    * 
+    * @param manager the manager for this session
     */
-   protected static StringManager sm =
-      StringManager.getManager(ClusteredSession.class.getPackage().getName());
+   protected ClusteredSession(ClusteredManager manager)
+   {
+      super();
+      this.manager = manager;
 
-   protected ClusteredSession(AbstractJBossManager manager, ReplicationTrigger replicationTrigger, boolean useJK)
-   {
-      super(manager);
-      invalidationPolicy = replicationTrigger;
-      this.useJK = useJK;
+      // Initialize access count
+      if (ACTIVITY_CHECK) {
+          accessCount = new AtomicInteger();
+      }
+      
+      invalidationPolicy = manager.getReplicationTrigger();
+      this.useJK = manager.getUseJK();
       this.firstAccess = true;
-      this.notificationPolicy = new LegacyClusteredSessionNotificationPolicy();
-      checkAlwaysReplicateTimestamp();
+      
+      int maxUnrep = manager.getMaxUnreplicatedInterval() * 1000;
+      setMaxUnreplicatedInterval(maxUnrep);
+      this.notificationPolicy = manager.getNotificationPolicy();
+      establishDistributedCacheManager();
    }
+   
+   // ---------------------------------------------------------------- Session
 
-   /**
-    * Check to see if the session data is still valid. Outdated here means 
-    * that the in-memory data is not in sync with one in the data store.
-    * 
-    * @return
-    */
-   public boolean isOutdated()
+   public abstract String getInfo();
+   
+   public String getAuthType()
    {
-      return thisAccessedTime < outdatedTime;
+      return (this.authType);
    }
-   
-   public void clearOutdated()
+
+   public void setAuthType(String authType)
    {
-      // Only overwrite the access time if access() hasn't been called
-      // since setOutdatedVersion() was called
-      if (outdatedTime > thisAccessedTime)
-      {
-         lastAccessedTime = thisAccessedTime;
-         thisAccessedTime = outdatedTime;
-      }
-      outdatedTime = 0;
-      
-      // Only overwrite the version if the outdated version is greater
-      // Otherwise when we first unmarshal a session that has been
-      // replicated many times, we will reset the version to 0
-      if (outdatedVersion > version)
-         version = outdatedVersion;
-      
-      outdatedVersion = 0;
+      String oldAuthType = this.authType;
+      this.authType = authType;
+      support.firePropertyChange("authType", oldAuthType, this.authType);
    }
-   
-   public void updateAccessTimeFromOutdatedTime()
+
+   public long getCreationTime()
    {
-      if (outdatedTime > thisAccessedTime)
-      {
-         lastAccessedTime = thisAccessedTime;
-         thisAccessedTime = outdatedTime;
-      }
-      outdatedTime = 0;
+      if (!isValidInternal())
+         throw new IllegalStateException
+             (sm.getString("clusteredSession.getCreationTime.ise"));
+
+     return (this.creationTime);
    }
-
+   
    /**
-    * Gets the session id with any appended jvmRoute info removed.
+    * Set the creation time for this session.  This method is called by the
+    * Manager when an existing Session instance is reused.
     *
-    * @see #getUseJK()
+    * @param time The new creation time
     */
-   public String getRealId()
+   public void setCreationTime(long time)
    {
-      return realId;
+      this.creationTime = time;
+      this.lastAccessedTime = time;
+      this.thisAccessedTime = time;
+      sessionMetadataDirty();
    }
 
-   private void parseRealId(String sessionId)
+   public String getId()
    {
-      String newId = null;
-      if (useJK)
-         newId = Util.getRealId(sessionId);
-      else
-         newId = sessionId;
-      
-      // realId is used in a lot of map lookups, so only replace it
-      // if the new id is actually different -- preserve object identity
-      if (!newId.equals(realId))
-         realId = newId;
+      return (this.id);
    }
 
+   public String getIdInternal()
+   {
+      return (this.id);
+   }
+   
    /**
-    * This is called specifically for failover case using mod_jk where the new 
-    * session has this node name in there. As a result, it is safe to just 
-    * replace the id since the backend store is using the "real" id
-    * without the node name.
-    * 
-    * @param id
+    * Overrides the superclass method to also set the
+    * {@link #getRealId() realId} property.
     */
-   public void resetIdWithRouteInfo(String id)
+   public void setId(String id)
    {
+      // Parse the real id first, as super.setId() calls add(),
+      // which depends on having the real id
+      parseRealId(id);
+      
+      if ((this.id != null) && (manager != null))
+          manager.remove(this);
+
       this.id = id;
-      parseRealId(id);
+      
+      this.clusterStatus = new ClusteredSessionManagementStatus(this.realId, true, null, null);
+
+      if (manager != null)
+          manager.add(this);
    }
 
-   public boolean getUseJK()
+   public long getLastAccessedTime()
    {
-      return useJK;
+      if (!isValidInternal()) {
+         throw new IllegalStateException
+             (sm.getString("clusteredSession.getLastAccessedTime.ise"));
+     }
+
+     return (this.lastAccessedTime);
    }
 
-   public int getVersion()
+   public long getLastAccessedTimeInternal()
    {
-      return version;
+      return (this.lastAccessedTime);
    }
 
-   /**
-    * Increment our version due to local changes.
-    * 
-    * @return the new version
-    */
-   public int incrementVersionFromLocalActivity()
+   public Manager getManager()
    {
-      return version++;
-   }   
-   
-   /**
-    * Update our version due to changes in the distributed cache.
-    * 
-    * @param version the distributed cache version
-    * @return <code>true</code>
-    */
-   public boolean setVersionFromDistributedCache(int version)
+      return (this.manager);
+   }
+
+   public void setManager(Manager manager)
    {
-      boolean outdated = this.version < version;
-      if (outdated)
-      {
-         this.outdatedVersion = version;
-         outdatedTime = System.currentTimeMillis();
-      }
-      return outdated;
+      if ((manager instanceof ClusteredManager) == false)
+         throw new IllegalArgumentException("manager must implement ClusteredManager");
+      
+      this.manager = (ClusteredManager) manager;
    }
 
+   public int getMaxInactiveInterval()
+   {
+      return (this.maxInactiveInterval);
+   }
+
    /**
     * Overrides the superclass to calculate 
     * {@link #getMaxUnreplicatedInterval() maxUnreplicatedInterval}.
     */
    public void setMaxInactiveInterval(int interval)
    {
-      super.setMaxInactiveInterval(interval);
+      this.maxInactiveInterval = interval;
+      if (isValid && interval == 0) 
+      {
+         expire();
+      }
       checkAlwaysReplicateTimestamp();
       sessionMetadataDirty();
    }
 
-   /**
-    * Gets the time {@link #updateLastReplicated()} was last called, or
-    * <code>0</code> if it has never been called.
-    */
-   public long getLastReplicated()
+   public Principal getPrincipal()
    {
-      return lastReplicated;
-   }
-   
+      return (this.principal);
+   }   
+
    /**
-    * Sets the {@link #getLastReplicated() lastReplicated} field to
-    * the current time.
+    * Set the authenticated Principal that is associated with this Session.
+    * This provides an <code>Authenticator</code> with a means to cache a
+    * previously authenticated Principal, and avoid potentially expensive
+    * <code>Realm.authenticate()</code> calls on every request.
+    *
+    * @param principal The new Principal, or <code>null</code> if none
     */
-   public void updateLastReplicated()
+   public void setPrincipal(Principal principal)
    {
-      lastReplicated = System.currentTimeMillis();
+      Principal oldPrincipal = this.principal;
+      this.principal = principal;
+      support.firePropertyChange("principal", oldPrincipal, this.principal);
+
+      if ((oldPrincipal != null && !oldPrincipal.equals(principal)) ||
+         (oldPrincipal == null && principal != null))
+         sessionMetadataDirty();
    }
 
-   public long getMaxUnreplicatedInterval()
+   public void access()
    {
-      return maxUnreplicatedInterval;
+      this.lastAccessedTime = this.thisAccessedTime;
+      this.thisAccessedTime = System.currentTimeMillis();
+      
+      if (ACTIVITY_CHECK) {
+          accessCount.incrementAndGet();
+      }
+      
+      // JBAS-3528. If it's not the first access, make sure
+      // the 'new' flag is correct
+      if (!firstAccess && isNew)
+      {
+         setNew(false);
+      }      
    }
    
-   public void setMaxUnreplicatedInterval(long interval)
+   public void endAccess()
    {
-      this.maxUnreplicatedInterval = Math.max(interval, -1);
-      checkAlwaysReplicateTimestamp();
-   }
-   
-   public boolean getMustReplicateTimestamp()
-   {
-      // If the access times are the same, access() was never called
-      // on the session
-      boolean touched = this.thisAccessedTime != this.lastAccessedTime;
-      boolean exceeds = alwaysReplicateTimestamp && touched;
+      isNew = false;
+
+      if (ACTIVITY_CHECK) {
+          accessCount.decrementAndGet();
+      }
       
-      if (!exceeds && touched && maxUnreplicatedInterval > 0) // -1 means ignore
+      this.lastAccessedTime = this.thisAccessedTime;
+      
+      if (firstAccess)
       {
-         long unrepl = System.currentTimeMillis() - lastReplicated;
-         exceeds = (unrepl >= maxUnreplicatedInterval);
-      }      
-      
-      return exceeds;
+         firstAccess = false;
+         // Tomcat marks the session as non new, but that's not really
+         // accurate per SRV.7.2, as the second request hasn't come in yet
+         // So, we fix that
+         isNew = true;
+      }
    }
-   
-   public DistributableSessionTimestamp getSessionTimestamp()
+
+   public boolean isNew()
    {
-      this.timestamp.setTimestamp(this.thisAccessedTime);
-      return this.timestamp;
+      if (!isValidInternal())
+         throw new IllegalStateException
+             (sm.getString("clusteredSession.isNew.ise"));
+
+     return (this.isNew);
    }
    
-   public DistributableSessionMetadata getSessionMetadata()
+   public void setNew(boolean isNew)
    {
-      this.metadata.setId(id);
-      this.metadata.setCreationTime(creationTime);
-      this.metadata.setMaxInactiveInterval(maxInactiveInterval);
-//      this.metadata.setInvalidationPolicy(invalidationPolicy);
-      this.metadata.setNew(isNew);
-      this.metadata.setValid(isValid);
+      this.isNew = isNew;
       
-      return this.metadata;
+      // Don't replicate metadata just 'cause its the second request
+      // The only effect of this is if someone besides a request 
+      // deserializes metadata from the distributed cache, this 
+      // field may be out of date.
+      // If a request accesses the session, the access() call will
+      // set isNew=false, so the request will see the correct value
+      // sessionMetadataDirty();
    }
-   
-   public Map<String, Object> getSessionAttributeMap()
+    
+    
+   /**
+    * Overrides the {@link StandardSession#isValid() superclass method}
+    * to call {@ #isValid(boolean) isValid(true)}.
+    */
+   public boolean isValid()
    {
-      return null;
+      return isValid(true);
    }
    
-   /**
-    * Gets the sessions creation time, skipping any validity check.
-    * 
-    * @return the creation time
-    */
-   public long getCreationTimeInternal()
+   public void setValid(boolean isValid)
    {
-      return creationTime;
+      this.isValid = isValid;
+      sessionMetadataDirty();
    }
 
    /**
-    * This is called after loading a session to initialize the transient values.
+    * Invalidates this session and unbinds any objects bound to it.
+    * Overridden here to remove across the cluster instead of just expiring.
     *
-    * @param manager
-    * @param cause the cause of the load
+    * @exception IllegalStateException if this method is called on
+    *  an invalidated session
     */
-   public abstract void initAfterLoad(AbstractJBossManager manager, ClusteredSessionNotificationCause cause);
+   public void invalidate()
+   {
+      if (!isValid())
+         throw new IllegalStateException(sm.getString("clusteredSession.invalidate.ise"));
 
-   /**
-    * Propogate session to the internal store.
-    */
-   public abstract void processSessionRepl();
+      // Cause this session to expire globally
+      boolean notify = true;
+      boolean localCall = true;
+      boolean localOnly = false;
+      expire(notify, localCall, localOnly, ClusteredSessionNotificationCause.INVALIDATE);
+   }
 
-   /**
-    * Remove myself from the internal store.
-    */
-   public abstract void removeMyself();
+   public void expire()
+   {
+      expire(true);
+   }
 
    /**
-    * Remove myself from the <t>local</t> internal store.
+    * Expires the session, but in such a way that other cluster nodes
+    * are unaware of the expiration.
+    *
+    * @param notify
     */
-   public abstract void removeMyselfLocal();
+   public void expire(boolean notify)
+   {
+      boolean localCall = true;
+      boolean localOnly = true;
+      expire(notify, localCall, localOnly, ClusteredSessionNotificationCause.TIMEOUT);
+   }
 
+   public void recycle()
+   {
+      // Reset the instance variables associated with this Session
+      attributes.clear();
+      setAuthType(null);
+      creationTime = 0L;
+      expiring = false;
+      id = null;
+      lastAccessedTime = 0L;
+      maxInactiveInterval = -1;
+      notes.clear();
+      setPrincipal(null);
+      isNew = false;
+      isValid = false;
+      manager = null;
+      
+      listeners.clear();
+      support = new PropertyChangeSupport(this);
+      
+      invalidationPolicy = ReplicationTrigger.ACCESS;
+      outdatedTime = 0;
+      outdatedVersion = 0;
+      sessionAttributesDirty = false;
+      sessionMetadataDirty = false;
+      realId = null;
+      useJK = false;
+      version.set(0);
+      hasActivationListener = null;
+      lastReplicated = 0;
+      maxUnreplicatedInterval = 0;
+      alwaysReplicateTimestamp = true;
+      this.notificationPolicy = null;
+      this.clusterStatus = null;
+      
+      distributedCacheManager = null;
+   }
+   
+   public void addSessionListener(SessionListener listener)
+   {
+      listeners.add(listener);
+   }
 
-   // ----------------------------------------------- Overridden Public Methods
+   public void removeSessionListener(SessionListener listener)
+   {
+      listeners.remove(listener);
+   }
 
-   public void access()
+   public Object getNote(String name)
    {
-      super.access();
-      
-      // JBAS-3528. If it's not the first access, make sure
-      // the 'new' flag is correct
-      if (!firstAccess && isNew)
-      {
-         setNew(false);
-      }      
+      return (notes.get(name));
    }
+
+   public Iterator getNoteNames()
+   {
+      return (notes.keySet().iterator());
+   }
+
+   public void setNote(String name, Object value)
+   {
+      notes.put(name, value);
+   }
+
+   public void removeNote(String name)
+   {
+      notes.remove(name);
+   }
+
+   // TODO uncomment when work on JBAS-1900 is completed
+//   public void removeNote(String name)
+//   {
+//      // FormAuthenticator removes the username and password because
+//      // it assumes they are not needed if the Principal is cached,
+//      // but they are needed if the session fails over, so ignore
+//      // the removal request.
+//      // TODO discuss this on Tomcat dev list to see if a better
+//      // way of handling this can be found
+//      if (Constants.SESS_USERNAME_NOTE.equals(name) 
+//            || Constants.SESS_PASSWORD_NOTE.equals(name))
+//      {
+//         if (log.isDebugEnabled())
+//         {
+//            log.debug("removeNote(): ignoring removal of note " + name);
+//         }
+//      }
+//      else
+//      {
+//         super.removeNote(name);
+//      }
+//      
+//   }
+
+   // TODO uncomment when work on JBAS-1900 is completed
+//   public void setNote(String name, Object value)
+//   {
+//      super.setNote(name, value);
+//      
+//      if (Constants.SESS_USERNAME_NOTE.equals(name) 
+//            || Constants.SESS_PASSWORD_NOTE.equals(name))
+//      {
+//         sessionIsDirty();
+//      }
+//   }
+
+   public HttpSession getSession()
+   {
+      if (facade == null){
+         if (SecurityUtil.isPackageProtectionEnabled()){
+             final HttpSession fsession = this;
+             facade = (StandardSessionFacade)AccessController.doPrivileged(new PrivilegedAction(){
+                 public Object run(){
+                     return new StandardSessionFacade(fsession);
+                 }
+             });
+         } else {
+             facade = new StandardSessionFacade(this);
+         }
+     }
+     return (facade);
+   }
    
-   
-   public void endAccess()
+   // ------------------------------------------------------------ HttpSession
+
+   public ServletContext getServletContext()
    {
-      super.endAccess();
-      
-      this.lastAccessedTime = this.thisAccessedTime;
-      
-      if (firstAccess)
-      {
-         firstAccess = false;
-         // Tomcat marks the session as non new, but that's not really
-         // accurate per SRV.7.2, as the second request hasn't come in yet
-         // So, we fix that
-         isNew = true;
-      }
+      if (manager == null)
+         return (null);
+     Context context = (Context) manager.getContainer();
+     if (context == null)
+         return (null);
+     else
+         return (context.getServletContext());
    }
 
    public Object getAttribute(String name)
    {
-
       if (!isValid())
          throw new IllegalStateException
             (sm.getString("clusteredSession.getAttribute.ise"));
@@ -499,7 +842,7 @@
       }
 
       // Validate our current state
-      if (!isValid())
+      if (!isValidInternal())
       {
          throw new IllegalStateException
             (sm.getString("clusteredSession.setAttribute.ise"));
@@ -511,14 +854,12 @@
             (sm.getString("clusteredSession.setAttribute.iae"));
       }
       
-      ClusteredSessionNotificationPolicy policy = getNotificationPolicy();
-      
       // Construct an event with the new value
       HttpSessionBindingEvent event = null;
 
       // Call the valueBound() method if necessary
       if (value instanceof HttpSessionBindingListener
-            && policy.isHttpSessionBindingListenerInvocationAllowed(this.clusterStatus, ClusteredSessionNotificationCause.MODIFY, name, true))
+            && notificationPolicy.isHttpSessionBindingListenerInvocationAllowed(this.clusterStatus, ClusteredSessionNotificationCause.MODIFY, name, true))
       {
          event = new HttpSessionBindingEvent(getSession(), name, value);
          try
@@ -527,17 +868,20 @@
          }
          catch (Throwable t)
          {
-             manager.getContainer().getLogger().error(sm.getString("standardSession.bindingEvent"), t);
+             manager.getContainer().getLogger().error(sm.getString("clusteredSession.bindingEvent"), t);
          }
       }
+      
+      if (value instanceof HttpSessionActivationListener)
+         hasActivationListener = Boolean.TRUE;
 
       // Replace or add this attribute
-      Object unbound = setInternalAttribute(name, value);
+      Object unbound = setAttributeInternal(name, value);
 
       // Call the valueUnbound() method if necessary
       if ((unbound != null) && (unbound != value) &&
          (unbound instanceof HttpSessionBindingListener) &&
-         policy.isHttpSessionBindingListenerInvocationAllowed(this.clusterStatus, ClusteredSessionNotificationCause.MODIFY, name, true))
+         notificationPolicy.isHttpSessionBindingListenerInvocationAllowed(this.clusterStatus, ClusteredSessionNotificationCause.MODIFY, name, true))
       {
          try
          {
@@ -546,12 +890,12 @@
          }
          catch (Throwable t)
          {
-             manager.getContainer().getLogger().error(sm.getString("standardSession.bindingEvent"), t);
+             manager.getContainer().getLogger().error(sm.getString("clusteredSession.bindingEvent"), t);
          }
       }
 
       // Notify interested application event listeners
-      if (policy.isHttpSessionAttributeListenerInvocationAllowed(this.clusterStatus, ClusteredSessionNotificationCause.MODIFY, name, true))
+      if (notificationPolicy.isHttpSessionAttributeListenerInvocationAllowed(this.clusterStatus, ClusteredSessionNotificationCause.MODIFY, name, true))
       {         
          Context context = (Context) manager.getContainer();
          Object lifecycleListeners[] = context.getApplicationEventListeners();
@@ -617,56 +961,426 @@
                {
                   ;
                }
-               manager.getContainer().getLogger().error(sm.getString("standardSession.attributeEvent"), t);
+               manager.getContainer().getLogger().error(sm.getString("clusteredSession.attributeEvent"), t);
             }
          }
       }
    }
 
+   public void removeAttribute(String name)
+   {
+      // Validate our current state
+      if (!isValidInternal())
+          throw new IllegalStateException
+              (sm.getString("clusteredSession.removeAttribute.ise"));
+      
+      final boolean localCall = true;
+      final boolean localOnly = false;
+      final boolean notify    = true;
+      removeAttributeInternal(name, localCall, localOnly, notify, ClusteredSessionNotificationCause.MODIFY);
+   }
 
+   @SuppressWarnings("deprecation")
+   public javax.servlet.http.HttpSessionContext getSessionContext()
+   {
+      return (sessionContext);
+   }
+
+   public Object getValue(String name)
+   {
+      return (getAttribute(name));
+   }
+
+   public String[] getValueNames()
+   {
+      if (!isValidInternal())
+         throw new IllegalStateException
+             (sm.getString("clusteredSession.getValueNames.ise"));
+
+     return (keys());
+   }
+
+   public void putValue(String name, Object value)
+   {
+      setAttribute(name, value);
+   }
+   
+   public void removeValue(String name) 
+   {
+      removeAttribute(name);
+   }
+   
+   // ---------------------------------------------------- DistributableSession
+   
+   public boolean getCreateRegionForSession()
+   {
+      // By default, regions are not needed. Subclasses that need them
+      // can override.
+      return false;
+   }
+   
+   public boolean needRegionForSession()
+   {
+      // By default, regions are not needed. Subclasses that need them
+      // can override.
+      return false;
+   }
+   
+   public boolean hasRegionForSession()
+   {
+      // By default, regions are not needed. Subclasses that need them
+      // can override.
+      return false;
+   }
+   
+   public void createdRegionForSession()
+   {
+      // By default, regions are not needed. Subclasses that need them
+      // can override.      
+   }
+
+   public int getVersion()
+   {
+      return version.get();
+   }
+   
+   public boolean getMustReplicateTimestamp()
+   {
+      // If the access times are the same, access() was never called
+      // on the session
+      boolean touched = this.thisAccessedTime != this.lastAccessedTime;
+      boolean exceeds = alwaysReplicateTimestamp && touched;
+      
+      if (!exceeds && touched && maxUnreplicatedInterval > 0) // -1 means ignore
+      {
+         long unrepl = System.currentTimeMillis() - lastReplicated;
+         exceeds = (unrepl >= maxUnreplicatedInterval);
+      }      
+      
+      return exceeds;
+   }
+   
+   public DistributableSessionTimestamp getSessionTimestamp()
+   {
+      this.timestamp.setTimestamp(this.thisAccessedTime);
+      return this.timestamp;
+   }
+   
+   public boolean isSessionMetadataDirty()
+   {
+      return sessionMetadataDirty;
+   }
+   
+   public DistributableSessionMetadata getSessionMetadata()
+   {
+      this.metadata.setId(id);
+      this.metadata.setCreationTime(creationTime);
+      this.metadata.setMaxInactiveInterval(maxInactiveInterval);
+      this.metadata.setNew(isNew);
+      this.metadata.setValid(isValid);
+      
+      return this.metadata;
+   }
+   
+   public boolean isSessionAttributeMapDirty()
+   {
+      return sessionAttributesDirty;
+   }
+
    /**
-    * Returns whether the attribute's type is one that can be replicated.
+    * {@inheritDoc}
     * 
-    * @param attribute  the attribute
-    * @return <code>true</code> if <code>attribute</code> is <code>null</code>,
-    *         <code>Serializable</code> or an array of primitives.
+    * @return this default impl always returns <code>null</code>
     */
-   protected boolean canAttributeBeReplicated(Object attribute)
+   public Map<String, Object> getSessionAttributeMap()
    {
-      if (attribute instanceof Serializable || attribute == null)
-         return true;
-      Class clazz = attribute.getClass().getComponentType();
-      return (clazz != null && clazz.isPrimitive());
+      return null;
    }
+   
+   public void update(Integer version, DistributableSessionTimestamp timestamp, 
+                      DistributableSessionMetadata metadata, Map attributes)
+   {
+      assert version != null : "version is null";
+      assert timestamp != null : "timestamp is null";
+      assert metadata != null : "metadata is null";
+      
+      this.version.set(version.intValue());
+      
+      this.lastAccessedTime = this.thisAccessedTime = timestamp.getTimestamp();
+      this.timestamp = timestamp;
+      
+      this.id = metadata.getId();
+      this.creationTime = metadata.getCreationTime();
+      this.maxInactiveInterval = metadata.getMaxInactiveInterval();
+      this.isNew = metadata.isNew();
+      this.isValid = metadata.isValid();
+      this.metadata = metadata;
+      
+      // Get our id without any jvmRoute appended
+      parseRealId(id);
+      
+      // We no longer know if we have an activationListener
+      hasActivationListener = null;
+      
+      // If the session has been replicated, any subsequent
+      // access cannot be the first.
+      this.firstAccess = false;
+      
+      // We don't know when we last replicated our timestamp. We may be
+      // getting called due to activation, not deserialization after
+      // replication, so this.timestamp may be after the last replication.
+      // So use the creation time as a conservative guesstimate.  Only downside 
+      // is we may replicate a timestamp earlier than we need to, which is not
+      // a heavy cost.
+      this.lastReplicated = this.creationTime;
+      
+      this.notificationPolicy = new LegacyClusteredSessionNotificationPolicy();
+      
+      this.clusterStatus = new ClusteredSessionManagementStatus(this.realId, true, null, null);
+      
+      checkAlwaysReplicateTimestamp();
+      
+      // TODO uncomment when work on JBAS-1900 is completed      
+//      // Session notes -- for FORM auth apps, allow replicated session 
+//      // to be used without requiring a new login
+//      // We use the superclass set/removeNote calls here to bypass
+//      // the custom logic we've added      
+//      String username     = (String) in.readObject();
+//      if (username != null)
+//      {
+//         super.setNote(Constants.SESS_USERNAME_NOTE, username);
+//      }
+//      else
+//      {
+//         super.removeNote(Constants.SESS_USERNAME_NOTE);
+//      }
+//      String password     = (String) in.readObject();
+//      if (password != null)
+//      {
+//         super.setNote(Constants.SESS_PASSWORD_NOTE, password);
+//      }
+//      else
+//      {
+//         super.removeNote(Constants.SESS_PASSWORD_NOTE);
+//      }
+      
+      // we ignore the attributes
+   }   
 
+   // ------------------------------------------------------------------ Public
+
    /**
-    * Invalidates this session and unbinds any objects bound to it.
-    * Overridden here to remove across the cluster instead of just expiring.
+    * This is called after loading a session to initialize the transient values.
     *
-    * @exception IllegalStateException if this method is called on
-    *  an invalidated session
+    * @param manager
+    * @param cause the cause of the load
     */
-   public void invalidate()
+   public void initAfterLoad(ClusteredManager manager, ClusteredSessionNotificationCause cause)
    {
-      if (!isValid())
-         throw new IllegalStateException(sm.getString("clusteredSession.invalidate.ise"));
+      // Our manager, notification policy and proxy may have been lost 
+      // if we were replicated, so reestablish them
+      this.manager = manager;
+      this.notificationPolicy = manager.getNotificationPolicy();
+      establishDistributedCacheManager();
 
-      // Cause this session to expire globally
-      boolean notify = true;
-      boolean localCall = true;
-      boolean localOnly = false;
-      expire(notify, localCall, localOnly, ClusteredSessionNotificationCause.INVALIDATE);
+      // Since attribute map may be transient, we may need to populate it 
+      // from the underlying store.
+      populateAttributes();
+      
+      if (cause == ClusteredSessionNotificationCause.ACTIVATION)
+      {
+         this.needsPostReplicateActivation = true;
+      }
+      
+      // Notify all attributes of type HttpSessionActivationListener (SRV 7.7.2)
+      this.notifyDidActivate(cause);
+      
+      // We are no longer outdated vis a vis distributed cache
+      clearOutdated();
    }
-    
-    
+
    /**
-    * Overrides the {@link StandardSession#isValid() superclass method}
-    * to call {@ #isValid(boolean) isValid(true)}.
+    * Increment our version and propagate ourself to the distributed cache.
     */
-   public boolean isValid()
+   public synchronized void processSessionReplication()
    {
-      return isValid(true);
+      // Replicate the session.
+      if (log.isTraceEnabled())
+      {
+         log.trace("processSessionRepl(): session is dirty. Will increment " +
+                   "version from: " + getVersion() + " and replicate.");
+      }
+      version.incrementAndGet();
+      distributedCacheManager.putSession(getRealId(), this);
+      
+      // Allow subclasses to replicate attributes if needed
+      replicateAttributes();
+      
+      sessionAttributesDirty = false;
+      sessionMetadataDirty = false;
+      
+      lastReplicated = System.currentTimeMillis();
    }
+
+   /**
+    * Remove myself from the distributed cache.
+    */
+   public abstract void removeMyself();
+
+   /**
+    * Remove myself from the <t>local</t> instance of the distributed cache.
+    */
+   public abstract void removeMyselfLocal();
+
+   /**
+    * Gets the session id with any appended jvmRoute info removed.
+    *
+    * @see #getUseJK()
+    */
+   public String getRealId()
+   {
+      return realId;
+   }
+   
+   /**
+    * Gets the sessions creation time, skipping any validity check.
+    * 
+    * @return the creation time
+    */
+   public long getCreationTimeInternal()
+   {
+      return creationTime;
+   }
+
+   /**
+    * Gets the time {@link #processSessionReplication()} was last called, or
+    * <code>0</code> if it has never been called.
+    */
+   public long getLastReplicated()
+   {
+      return lastReplicated;
+   }
+
+   /**
+    * Gets the maximum period in ms after which a request accessing this
+    * session will trigger replication of its timestamp, even if the
+    * request doesn't otherwise modify the session. A value of -1 means
+    * no limit.
+    */
+   public long getMaxUnreplicatedInterval()
+   {
+      return maxUnreplicatedInterval;
+   }
+   
+   /**
+    * Sets the maximum period in ms after which a request accessing this
+    * session will trigger replication of its timestamp, even if the
+    * request doesn't otherwise modify the session. A value of -1 means
+    * no limit.
+    */
+   public void setMaxUnreplicatedInterval(long interval)
+   {
+      this.maxUnreplicatedInterval = Math.max(interval, -1);
+      checkAlwaysReplicateTimestamp();
+   }
+
+   /**
+    * This is called specifically for failover case using mod_jk where the new 
+    * session has this node name in there. As a result, it is safe to just 
+    * replace the id since the backend store is using the "real" id
+    * without the node name.
+    * 
+    * @param id
+    */
+   public void resetIdWithRouteInfo(String id)
+   {
+      this.id = id;
+      parseRealId(id);
+   }
+
+   /**
+    * Gets whether the session expects to be accessible via mod_jk, mod_proxy, 
+    * mod_cluster or other such AJP-based load balancers.
+    */
+   public boolean getUseJK()
+   {
+      return useJK;
+   }
+   
+   /**
+    * Update our version due to changes in the distributed cache.
+    * 
+    * @param version the distributed cache version
+    * @return <code>true</code>
+    */
+   public boolean setVersionFromDistributedCache(int version)
+   {
+      boolean outdated = getVersion() < version;
+      if (outdated)
+      {
+         this.outdatedVersion = version;
+         outdatedTime = System.currentTimeMillis();
+      }
+      return outdated;
+   }   
+   
+   /**
+    * Check to see if the session data is still valid. Outdated here means 
+    * that the in-memory data is not in sync with one in the data store.
+    * 
+    * @return
+    */
+   public boolean isOutdated()
+   {
+      return thisAccessedTime < outdatedTime;
+   } 
+   
+   public boolean isSessionDirty()
+   {
+      return sessionAttributesDirty || sessionMetadataDirty;
+   } 
+
+   /** Inform any HttpSessionListener of the creation of this session */
+   public void tellNew(ClusteredSessionNotificationCause cause)
+   {
+      // Notify interested session event listeners
+      fireSessionEvent(Session.SESSION_CREATED_EVENT, null);
+
+      // Notify interested application event listeners
+      if (notificationPolicy.isHttpSessionListenerInvocationAllowed(this.clusterStatus, cause, true))
+      {
+         Context context = (Context) manager.getContainer();
+         Object lifecycleListeners[] = context.getApplicationLifecycleListeners();
+         if (lifecycleListeners != null)
+         {
+            HttpSessionEvent event = new HttpSessionEvent(getSession());
+            for (int i = 0; i < lifecycleListeners.length; i++)
+            {
+               if (!(lifecycleListeners[i] instanceof HttpSessionListener))
+                  continue;
+               HttpSessionListener listener = (HttpSessionListener) lifecycleListeners[i];
+               try
+               {
+                  fireContainerEvent(context, "beforeSessionCreated", listener);
+                  listener.sessionCreated(event);
+                  fireContainerEvent(context, "afterSessionCreated", listener);
+               }
+               catch (Throwable t)
+               {
+                  try
+                  {
+                     fireContainerEvent(context, "afterSessionCreated", listener);
+                  }
+                  catch (Exception e)
+                  {
+                     ;
+                  }
+                  manager.getContainer().getLogger().error(sm.getString("clusteredSession.sessionEvent"), t);
+               }
+            }
+         }
+      }
+   }
     
    /**
     * Returns whether the current session is still valid, but
@@ -711,19 +1425,6 @@
    }
 
    /**
-    * Expires the session, but in such a way that other cluster nodes
-    * are unaware of the expiration.
-    *
-    * @param notify
-    */
-   public void expire(boolean notify)
-   {
-      boolean localCall = true;
-      boolean localOnly = true;
-      expire(notify, localCall, localOnly, ClusteredSessionNotificationCause.TIMEOUT);
-   }
-
-   /**
     * Expires the session, notifying listeners and possibly the manager.
     * <p>
     * <strong>NOTE:</strong> The manager will only be notified of the expiration
@@ -747,7 +1448,8 @@
     *                   <code>true</code>.
     * @param cause the cause of the expiration
     */
-   public void expire(boolean notify, boolean localCall, boolean localOnly, ClusteredSessionNotificationCause cause)
+   public void expire(boolean notify, boolean localCall, boolean localOnly, 
+                      ClusteredSessionNotificationCause cause)
    {
       if (log.isTraceEnabled())
       {
@@ -777,7 +1479,7 @@
          Object lifecycleListeners[] = context.getApplicationLifecycleListeners();
          if (notify 
                && (lifecycleListeners != null) 
-               && getNotificationPolicy().isHttpSessionListenerInvocationAllowed(this.clusterStatus, cause, localCall))
+               && notificationPolicy.isHttpSessionListenerInvocationAllowed(this.clusterStatus, cause, localCall))
          {
             HttpSessionEvent event =
                new HttpSessionEvent(getSession());
@@ -810,7 +1512,7 @@
                   {
                      ;
                   }
-                  manager.getContainer().getLogger().error(sm.getString("standardSession.sessionEvent"), t);
+                  manager.getContainer().getLogger().error(sm.getString("clusteredSession.sessionEvent"), t);
                }
             }
          }
@@ -828,10 +1530,17 @@
          // JBAS-1360 -- Unbind any objects associated with this session
          String keys[] = keys();
          for (int i = 0; i < keys.length; i++)
+         {
              removeAttributeInternal(keys[i], localCall, localOnly, notify, cause);
+         }
 
          // Remove this session from our manager's active sessions
-         removeFromManager(localCall, localOnly);
+         // If !localCall, this expire call came from the manager, 
+         // so don't recurse
+         if (localCall)
+         {
+            removeFromManager(localOnly);
+         }
 
          // We have completed expire of this session
          setValid(false);
@@ -839,36 +1548,15 @@
       }
 
    }
-   
+
    /**
-    * Advise our manager to remove this expired session.
+    * Inform any HttpSessionActivationListener that the session will passivate.
     * 
-    * @param localCall whether this call originated from local activity
-    *                  or from a remote invalidation.  In this default
-    *                  implementation, this parameter is ignored.
-    * @param localOnly whether the rest of the cluster should be made aware
-    *                  of the removal
+    * @param cause cause of the notification (e.g. {@link ClusteredSessionNotificationCause#REPLICATION}
+    *              or {@link ClusteredSessionNotificationCause#PASSIVATION}
     */
-   protected void removeFromManager(boolean localCall, boolean localOnly)
+   public void notifyWillPassivate(ClusteredSessionNotificationCause cause)
    {
-      if(localOnly)
-      {
-          ((AbstractJBossManager) manager).removeLocal(this);
-      } 
-      else
-      {
-         manager.remove(this);
-      }
-   }
-
-   @Override
-   public void passivate()
-   {      
-      passivate(ClusteredSessionNotificationCause.PASSIVATION);
-   }
-
-   public void passivate(ClusteredSessionNotificationCause cause)
-   {
       // Notify interested session event listeners
       fireSessionEvent(Session.SESSION_PASSIVATED_EVENT, null);
 
@@ -876,8 +1564,6 @@
       {
          boolean hasListener = false;
          
-         ClusteredSessionNotificationPolicy policy = getNotificationPolicy();
-         
          // Notify ActivationListeners
          HttpSessionEvent event = null;
          String keys[] = keys();
@@ -889,7 +1575,7 @@
             {
                hasListener = true;
                
-               if (policy.isHttpSessionActivationListenerInvocationAllowed(this.clusterStatus, cause, keys[i]))
+               if (notificationPolicy.isHttpSessionActivationListenerInvocationAllowed(this.clusterStatus, cause, keys[i]))
                {                  
                   if (event == null)
                      event = new HttpSessionEvent(getSession());
@@ -916,13 +1602,14 @@
       }
    }
 
-   public void activate()
+   /**
+    * Inform any HttpSessionActivationListener that the session has been activated.
+    * 
+    * @param cause cause of the notification (e.g. {@link ClusteredSessionNotificationCause#REPLICATION}
+    *              or {@link ClusteredSessionNotificationCause#PASSIVATION}
+    */
+   public void notifyDidActivate(ClusteredSessionNotificationCause cause)
    {
-      activate(ClusteredSessionNotificationCause.ACTIVATION);
-   }
-
-   public void activate(ClusteredSessionNotificationCause cause)
-   {
       // Notify interested session event listeners
       fireSessionEvent(Session.SESSION_ACTIVATED_EVENT, null);
 
@@ -932,8 +1619,6 @@
 
          boolean hasListener = false;
          
-         ClusteredSessionNotificationPolicy policy = getNotificationPolicy();
-         
          HttpSessionEvent event = null;
          String keys[] = keys();
          Map attrs = getAttributesInternal();
@@ -944,7 +1629,7 @@
             {
                hasListener = true;
                
-               if (policy.isHttpSessionActivationListenerInvocationAllowed(this.clusterStatus, cause, keys[i]))
+               if (notificationPolicy.isHttpSessionActivationListenerInvocationAllowed(this.clusterStatus, cause, keys[i]))
                {
                   if (event == null)
                      event = new HttpSessionEvent(getSession());
@@ -970,214 +1655,74 @@
       }
    }
    
+   /**
+    * Gets whether the session needs to notify HttpSessionActivationListeners
+    * that it has been activated following replication.
+    */
    public boolean getNeedsPostReplicateActivation()
    {
       return needsPostReplicateActivation;
    }
 
-   // TODO uncomment when work on JBAS-1900 is completed
-//   public void removeNote(String name)
-//   {
-//      // FormAuthenticator removes the username and password because
-//      // it assumes they are not needed if the Principal is cached,
-//      // but they are needed if the session fails over, so ignore
-//      // the removal request.
-//      // TODO discuss this on Tomcat dev list to see if a better
-//      // way of handling this can be found
-//      if (Constants.SESS_USERNAME_NOTE.equals(name) 
-//            || Constants.SESS_PASSWORD_NOTE.equals(name))
-//      {
-//         if (log.isDebugEnabled())
-//         {
-//            log.debug("removeNote(): ignoring removal of note " + name);
-//         }
-//      }
-//      else
-//      {
-//         super.removeNote(name);
-//      }
-//      
-//   }
+   @Override
+   public String toString()
+   {
+      return new StringBuilder(getClass().getSimpleName())
+        .append('[').append("id: ").append(id)
+        .append(" lastAccessedTime: ").append(lastAccessedTime)
+        .append(" version: ").append(version)
+        .append(" lastOutdated: ").append(outdatedTime).append(']')
+        .toString();
+   }
 
-   // TODO uncomment when work on JBAS-1900 is completed
-//   public void setNote(String name, Object value)
-//   {
-//      super.setNote(name, value);
-//      
-//      if (Constants.SESS_USERNAME_NOTE.equals(name) 
-//            || Constants.SESS_PASSWORD_NOTE.equals(name))
-//      {
-//         sessionIsDirty();
-//      }
-//   }
+   // ----------------------------------------------------- Protected Methods
 
-   /**
-    * Override the superclass to additionally reset this class' fields.
-    * <p>
-    * <strong>NOTE:</strong> It is not anticipated that this method will be
-    * called on a ClusteredSession, but we are overriding the method to be
-    * thorough.
-    * </p>
-    */
-   public void recycle()
-   {
-      super.recycle();
-      
-      // Fields that the superclass isn't clearing
-      listeners.clear();
-      support = new PropertyChangeSupport(this);
-      
-      invalidationPolicy = ReplicationTrigger.ACCESS;
-      outdatedTime = 0;
-      outdatedVersion = 0;
-      sessionAttributesDirty = false;
-      sessionMetadataDirty = false;
-      realId = null;
-      useJK = false;
-      version = 0;
-      hasActivationListener = null;
-      lastReplicated = 0;
-      maxUnreplicatedInterval = 0;
-      alwaysReplicateTimestamp = true;
-      this.notificationPolicy = null;
-      this.clusterStatus = null;
-   }
+   protected abstract Object getAttributeInternal(String name);
+
+   protected abstract Object setAttributeInternal(String name, Object value);
+
+   protected abstract Object removeAttributeInternal(String name, boolean localCall, boolean localOnly);
    
    /**
-    * Set the creation time for this session.  This method is called by the
-    * Manager when an existing Session instance is reused.
-    *
-    * @param time The new creation time
+    * Extension point for subclasses to load the attribute map from the
+    * distributed cache.
     */
-   public void setCreationTime(long time)
-   {
-      super.setCreationTime(time);
-      sessionMetadataDirty();
-   }
+   protected abstract void populateAttributes();
    
-   /**
-    * Overrides the superclass method to also set the
-    * {@link #getRealId() realId} property.
-    */
-   public void setId(String id)
+   protected final Map<String, Object> getAttributesInternal()
    {
-      // Parse the real id first, as super.setId() calls add(),
-      // which depends on having the real id
-      parseRealId(id);
-      
-      // TODO -- should we bypass this if realId hasn't changed? We're removing
-      // and readding every time we fail over, when all we want is a 
-      // jvmRoute change to the session id
-      
-      if ((this.id != null) && (manager != null))
-          manager.remove(this);
-
-      this.id = id;
-      
-      this.clusterStatus = new ClusteredSessionManagementStatus(this.realId, true, null, null);
-
-      if (manager != null)
-          manager.add(this);
+      return attributes;
    }
    
-   
-
-   /**
-    * Set the authenticated Principal that is associated with this Session.
-    * This provides an <code>Authenticator</code> with a means to cache a
-    * previously authenticated Principal, and avoid potentially expensive
-    * <code>Realm.authenticate()</code> calls on every request.
-    *
-    * @param principal The new Principal, or <code>null</code> if none
-    */
-   public void setPrincipal(Principal principal)
+   protected final ClusteredManager getManagerInternal()
    {
-
-      Principal oldPrincipal = this.principal;
-      this.principal = principal;
-      support.firePropertyChange("principal", oldPrincipal, this.principal);
-
-      if ((oldPrincipal != null && !oldPrincipal.equals(principal)) ||
-         (oldPrincipal == null && principal != null))
-         sessionMetadataDirty();
-
+      return manager;
    }
    
-   public void setNew(boolean isNew)
+   protected final D getDistributedCacheManager()
    {
-      super.setNew(isNew);
-      // Don't replicate metadata just 'cause its the second request
-      // The only effect of this is if someone besides a request 
-      // deserializes metadata from the distributed cache, this 
-      // field may be out of date.
-      // If a request accesses the session, the access() call will
-      // set isNew=false, so the request will see the correct value
-      // sessionMetadataDirty();
+      return distributedCacheManager;
    }
    
-   public void setValid(boolean isValid)
+   protected final void setDistributedCacheManager(D distributedCacheManager)
    {
-      super.setValid(isValid);
-      sessionMetadataDirty();
-   }
+      this.distributedCacheManager = distributedCacheManager;
+   }   
 
-   @Override
-   public void tellNew() 
+   /**
+    * Returns whether the attribute's type is one that can be replicated.
+    * 
+    * @param attribute  the attribute
+    * @return <code>true</code> if <code>attribute</code> is <code>null</code>,
+    *         <code>Serializable</code> or an array of primitives.
+    */
+   protected boolean canAttributeBeReplicated(Object attribute)
    {
-      tellNew(ClusteredSessionNotificationCause.CREATE);
+      if (attribute instanceof Serializable || attribute == null)
+         return true;
+      Class clazz = attribute.getClass().getComponentType();
+      return (clazz != null && clazz.isPrimitive());
    }
-
-   public void tellNew(ClusteredSessionNotificationCause cause)
-   {
-      // Notify interested session event listeners
-      fireSessionEvent(Session.SESSION_CREATED_EVENT, null);
-
-      // Notify interested application event listeners
-      if (getNotificationPolicy().isHttpSessionListenerInvocationAllowed(this.clusterStatus, cause, true))
-      {
-         Context context = (Context) manager.getContainer();
-         Object lifecycleListeners[] = context.getApplicationLifecycleListeners();
-         if (lifecycleListeners != null)
-         {
-            HttpSessionEvent event = new HttpSessionEvent(getSession());
-            for (int i = 0; i < lifecycleListeners.length; i++)
-            {
-               if (!(lifecycleListeners[i] instanceof HttpSessionListener))
-                  continue;
-               HttpSessionListener listener = (HttpSessionListener) lifecycleListeners[i];
-               try
-               {
-                  fireContainerEvent(context, "beforeSessionCreated", listener);
-                  listener.sessionCreated(event);
-                  fireContainerEvent(context, "afterSessionCreated", listener);
-               }
-               catch (Throwable t)
-               {
-                  try
-                  {
-                     fireContainerEvent(context, "afterSessionCreated", listener);
-                  }
-                  catch (Exception e)
-                  {
-                     ;
-                  }
-                  manager.getContainer().getLogger().error(sm.getString("standardSession.sessionEvent"), t);
-               }
-            }
-         }
-      }
-   }
-
-   public String toString()
-   {
-      StringBuffer buf = new StringBuffer();
-      buf.append("id: " +id).append(" lastAccessedTime: " +lastAccessedTime).append(
-              " version: " +version).append(" lastOutdated: " + outdatedTime);
-
-      return buf.toString();
-   }
-
-   // ----------------------------------------------------- Protected Methods
    
    /**
     * Removes any attribute whose name is found in {@link #excludedAttributes}
@@ -1190,7 +1735,7 @@
     *         <code>attributes</code>, or <code>null</code> if no attributes
     *         were removed.
     */
-   protected static Map removeExcludedAttributes(Map attributes)
+   protected final Map removeExcludedAttributes(Map attributes)
    {
       Map excluded = null;
       for (int i = 0; i < excludedAttributes.length; i++) {
@@ -1213,111 +1758,95 @@
       return excluded;      
    }
 
-   protected ClusteredSessionNotificationPolicy getNotificationPolicy()
+   protected final boolean isGetDirty(Object attribute)
    {
-      return notificationPolicy;
+      boolean result = false;
+      switch (invalidationPolicy)
+      {
+         case SET_AND_GET:
+            result = true;
+            break;
+         case SET_AND_NON_PRIMITIVE_GET:
+            result = isMutable(attribute);
+            break;
+         default:
+            // result is false
+      }
+      return result;
    }
-
-   protected void setNotificationPolicy(ClusteredSessionNotificationPolicy notificationPolicy)
-   {
-      this.notificationPolicy = notificationPolicy;
-   } 
    
-   public void update(Integer version, DistributableSessionTimestamp timestamp, 
-                      DistributableSessionMetadata metadata, Map attributes)
+   protected boolean isMutable(Object attribute)
    {
-      assert version != null : "version is null";
-      assert timestamp != null : "timestamp is null";
-      assert metadata != null : "metadata is null";
-      
-      this.version = version.intValue();
-      
-      this.lastAccessedTime = this.thisAccessedTime = timestamp.getTimestamp();
-      this.timestamp = timestamp;
-      
-      this.id = metadata.getId();
-      this.creationTime = metadata.getCreationTime();
-      this.maxInactiveInterval = metadata.getMaxInactiveInterval();
-      this.isNew = metadata.isNew();
-      this.isValid = metadata.isValid();
-//      this.invalidationPolicy = metadata.getInvalidationPolicy();
-      this.metadata = metadata;
-      
-      // Get our id without any jvmRoute appended
-      parseRealId(id);
-      
-      // We no longer know if we have an activationListener
-      hasActivationListener = null;
-      
-      // If the session has been replicated, any subsequent
-      // access cannot be the first.
-      this.firstAccess = false;
-      
-      // We don't know when we last replicated our timestamp. We may be
-      // getting called due to activation, not deserialization after
-      // replication, so this.timestamp may be after the last replication.
-      // So use the creation time as a conservative guesstimate.  Only downside 
-      // is we may replicate a timestamp earlier than we need to, which is not
-      // a heavy cost.
-      this.lastReplicated = this.creationTime;
-      
-      this.notificationPolicy = new LegacyClusteredSessionNotificationPolicy();
-      
-      this.clusterStatus = new ClusteredSessionManagementStatus(this.realId, true, null, null);
-      
-      checkAlwaysReplicateTimestamp();
-      
-      // TODO uncomment when work on JBAS-1900 is completed      
-//      // Session notes -- for FORM auth apps, allow replicated session 
-//      // to be used without requiring a new login
-//      // We use the superclass set/removeNote calls here to bypass
-//      // the custom logic we've added      
-//      String username     = (String) in.readObject();
-//      if (username != null)
-//      {
-//         super.setNote(Constants.SESS_USERNAME_NOTE, username);
-//      }
-//      else
-//      {
-//         super.removeNote(Constants.SESS_USERNAME_NOTE);
-//      }
-//      String password     = (String) in.readObject();
-//      if (password != null)
-//      {
-//         super.setNote(Constants.SESS_PASSWORD_NOTE, password);
-//      }
-//      else
-//      {
-//         super.removeNote(Constants.SESS_PASSWORD_NOTE);
-//      }
-      
-      // we ignore the attributes
-   }   
+      return attribute != null &&
+                !(attribute instanceof String ||
+                  attribute instanceof Number ||
+                  attribute instanceof Character ||
+                  attribute instanceof Boolean);
+   }
    
-   // -------------------------------------- Internal protected method override
-
    /**
-    * Method inherited from Tomcat. Return zero-length based string if not found.
+    * Gets a reference to the JBossCacheService.
     */
-   protected String[] keys()
+   protected void establishDistributedCacheManager()
    {
-      return ((String[]) getAttributesInternal().keySet().toArray(EMPTY_ARRAY));
+      if (distributedCacheManager == null)
+      {
+         distributedCacheManager = (D) getManagerInternal().getDistributedCacheManager();
+
+         // still null???
+         if (distributedCacheManager == null)
+         {
+            throw new RuntimeException("DistributedCacheManager is null.");
+         }
+      }
    }
-
+   
    /**
-    * Called by super.removeAttribute().
-    * 
-    * @param name      the attribute name 
-    * @param notify    <code>true</code> if listeners should be notified
+    * Extension point for subclasses to handle replication of attributes.
+    * This default implementation does nothing.
     */
-   @Override
-   protected void removeAttributeInternal(String name, boolean notify)
+   protected void replicateAttributes()
    {
-      boolean localCall = true;
-      boolean localOnly = false;
-      removeAttributeInternal(name, localCall, localOnly, notify, ClusteredSessionNotificationCause.MODIFY);
+      // no-op
    }
+
+   protected final void sessionAttributesDirty()
+   {
+      if (!sessionAttributesDirty && log.isTraceEnabled())
+         log.trace("Marking session attributes dirty " + id);
+      
+      sessionAttributesDirty = true;
+   }
    
+   protected final void setHasActivationListener(boolean hasListener)
+   {
+      this.hasActivationListener = Boolean.valueOf(hasListener);
+   }
+
+   // ----------------------------------------------------------------- Private
+   
+   private void checkAlwaysReplicateTimestamp()
+   {
+      this.alwaysReplicateTimestamp = 
+         (maxUnreplicatedInterval == 0 
+            || (maxUnreplicatedInterval > 0 && maxInactiveInterval >= 0 
+                  && maxUnreplicatedInterval > (maxInactiveInterval * 1000)));
+   }
+
+   private void parseRealId(String sessionId)
+   {
+      String newId = null;
+      if (useJK)
+         newId = Util.getRealId(sessionId);
+      else
+         newId = sessionId;
+      
+      // realId is used in a lot of map lookups, so only replace it
+      // if the new id is actually different -- preserve object identity
+      if (!newId.equals(realId))
+         realId = newId;
+   }
+   
    /**
     * Remove the attribute from the local cache and possibly the distributed
     * cache, plus notify any listeners
@@ -1333,14 +1862,14 @@
     * @param notify    <code>true</code> if listeners should be notified
     * @param cause the cause of the removal
     */
-   protected void removeAttributeInternal(String name, 
+   private void removeAttributeInternal(String name, 
                                           boolean localCall, 
                                           boolean localOnly,
                                           boolean notify, 
                                           ClusteredSessionNotificationCause cause)
    {
       // Remove this attribute from our collection
-      Object value = removeJBossInternalAttribute(name, localCall, localOnly);
+      Object value = removeAttributeInternal(name, localCall, localOnly);
 
       // Do we need to do valueUnbound() and attributeRemoved() notification?
       if (!notify || (value == null))
@@ -1348,19 +1877,17 @@
          return;
       }
 
-      ClusteredSessionNotificationPolicy policy = getNotificationPolicy();
-      
       // Call the valueUnbound() method if necessary
       HttpSessionBindingEvent event = null;
       if (value instanceof HttpSessionBindingListener
-            && policy.isHttpSessionBindingListenerInvocationAllowed(this.clusterStatus, cause, name, localCall))
+            && notificationPolicy.isHttpSessionBindingListenerInvocationAllowed(this.clusterStatus, cause, name, localCall))
       {
          event = new HttpSessionBindingEvent(getSession(), name, value);
          ((HttpSessionBindingListener) value).valueUnbound(event);
       }
 
       // Notify interested application event listeners
-      if (policy.isHttpSessionAttributeListenerInvocationAllowed(this.clusterStatus, cause, name, localCall))
+      if (notificationPolicy.isHttpSessionAttributeListenerInvocationAllowed(this.clusterStatus, cause, name, localCall))
       {
          Context context = (Context) manager.getContainer();
          Object lifecycleListeners[] = context.getApplicationEventListeners();
@@ -1399,110 +1926,129 @@
                {
                   ;
                }
-               manager.getContainer().getLogger().error(sm.getString("standardSession.attributeEvent"), t);
+               manager.getContainer().getLogger().error(sm.getString("clusteredSession.attributeEvent"), t);
             }
          }
       }
-
    }
-
-   protected Object getAttributeInternal(String name)
+   
+   private String[] keys()
    {
-      return getJBossInternalAttribute(name);
+      Set keySet = getAttributesInternal().keySet();
+      return ((String[]) keySet.toArray(new String[keySet.size()]));
    }
 
-   protected Map getAttributesInternal()
+   /**
+    * Return the <code>isValid</code> flag for this session without any expiration
+    * check.
+    */
+   private boolean isValidInternal() 
    {
-      return getJBossInternalAttributes();
+       return (this.isValid || this.expiring);
    }
-
-   protected Object setInternalAttribute(String name, Object value)
+   
+   /**
+    * Fire container events if the Context implementation is the
+    * <code>org.apache.catalina.core.StandardContext</code>.
+    *
+    * @param context Context for which to fire events
+    * @param type Event type
+    * @param data Event data
+    *
+    * @exception Exception occurred during event firing
+    */
+   private void fireContainerEvent(Context context,
+                                   String type, Object data)
+       throws Exception 
    {
-      if (value instanceof HttpSessionActivationListener)
-         hasActivationListener = Boolean.TRUE;
-      
-      return setJBossInternalAttribute(name, value);
-   }
 
-   // ------------------------------------------ JBoss internal abstract method
+       if (!"org.apache.catalina.core.StandardContext".equals
+           (context.getClass().getName())) 
+       {
+           return; // Container events are not supported
+       }
+       // NOTE:  Race condition is harmless, so do not synchronize
+       if (containerEventMethod == null) 
+       {
+           containerEventMethod =
+               context.getClass().getMethod("fireContainerEvent",
+                                            containerEventTypes);
+       }
+       Object containerEventParams[] = new Object[2];
+       containerEventParams[0] = type;
+       containerEventParams[1] = data;
+       containerEventMethod.invoke(context, containerEventParams);
 
-   protected abstract Object getJBossInternalAttribute(String name);
+   }                                  
 
-   protected abstract Object removeJBossInternalAttribute(String name, boolean localCall, boolean localOnly);
+   /**
+    * Notify all session event listeners that a particular event has
+    * occurred for this Session.  The default implementation performs
+    * this notification synchronously using the calling thread.
+    *
+    * @param type Event type
+    * @param data Event data
+    */
+   private void fireSessionEvent(String type, Object data) 
+   {
+       if (listeners.size() < 1)
+           return;
+       SessionEvent event = new SessionEvent(this, type, data);
+       SessionListener list[] = new SessionListener[0];
+       synchronized (listeners) 
+       {
+           list = (SessionListener[]) listeners.toArray(list);
+       }
 
-   protected abstract Map getJBossInternalAttributes();
+       for (int i = 0; i < list.length; i++)
+       {
+           ((SessionListener) list[i]).sessionEvent(event);
+       }
 
-   protected abstract Object setJBossInternalAttribute(String name, Object value);
-
-   // ------------------------------------------------ Session Package Methods
-
-   protected void sessionAttributesDirty()
-   {
-      if (!sessionAttributesDirty && log.isTraceEnabled())
-         log.trace("Marking session attributes dirty " + id);
-      
-      sessionAttributesDirty = true;
    }
    
-   protected void sessionMetadataDirty()
+   private void sessionMetadataDirty()
    {
       if (!sessionMetadataDirty && !isNew && log.isTraceEnabled())
          log.trace("Marking session metadata dirty " + id);
       sessionMetadataDirty = true;
    }
    
-   public boolean isSessionMetadataDirty()
+   /**
+    * Advise our manager to remove this expired session.
+    * @param localOnly whether the rest of the cluster should be made aware
+    *                  of the removal
+    */
+   private void removeFromManager(boolean localOnly)
    {
-      return sessionMetadataDirty;
+      if(localOnly)
+      {
+         manager.removeLocal(this);
+      } 
+      else
+      {
+         manager.remove(this);
+      }
    }
 
-   public boolean isSessionDirty()
+   private final void clearOutdated()
    {
-      return sessionAttributesDirty || sessionMetadataDirty;
-   }
-   
-   public boolean isSessionAttributeMapDirty()
-   {
-      return sessionAttributesDirty;
-   }
-   
-   public boolean getCreateRegionForSession()
-   {
-      return false;
-   }
-
-   protected boolean isGetDirty(Object attribute)
-   {
-      boolean result = false;
-      switch (invalidationPolicy)
+      // Only overwrite the access time if access() hasn't been called
+      // since setOutdatedVersion() was called
+      if (outdatedTime > thisAccessedTime)
       {
-         case SET_AND_GET:
-            result = true;
-            break;
-         case SET_AND_NON_PRIMITIVE_GET:
-            result = isMutable(attribute);
-            break;
-         default:
-            // result is false
+         lastAccessedTime = thisAccessedTime;
+         thisAccessedTime = outdatedTime;
       }
-      return result;
+      outdatedTime = 0;
+      
+      // Only overwrite the version if the outdated version is greater
+      // Otherwise when we first unmarshall a session that has been
+      // replicated many times, we will reset the version to 0
+      if (outdatedVersion > version.get())
+         version.set(outdatedVersion);
+      
+      outdatedVersion = 0;
    }
-   
-   protected boolean isMutable(Object attribute)
-   {
-      return attribute != null &&
-                !(attribute instanceof String ||
-                  attribute instanceof Number ||
-                  attribute instanceof Character ||
-                  attribute instanceof Boolean);
-   }
-   
-   private void checkAlwaysReplicateTimestamp()
-   {
-      this.alwaysReplicateTimestamp = 
-         (maxUnreplicatedInterval == 0 
-            || (maxUnreplicatedInterval > 0 && maxInactiveInterval >= 0 
-                  && maxUnreplicatedInterval > (maxInactiveInterval * 1000)));
-   }
 
-}
+}
\ No newline at end of file

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/FieldBasedClusteredSession.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/FieldBasedClusteredSession.java	2008-10-23 18:15:21 UTC (rev 80002)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/FieldBasedClusteredSession.java	2008-10-23 18:55:56 UTC (rev 80003)
@@ -22,8 +22,6 @@
 package org.jboss.web.tomcat.service.session;
 
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
@@ -49,18 +47,7 @@
  * required to be aspectized. That is, you can't simply declare them as Serializable. This is restricted
  * because of the marshalling/unmarshalling issue.</p>
  *
- * <p>We use JBossCache for our internal, replicated data store.
- * The internal structure is like in JBossCache:
- * <pre>
- * /JSESSION
- *    /hostname
- *       /web_app_path    (path + session id is unique)
- *           /id   Map(id, session)
- *                    (VERSION, version)
- *              /ATTRIBUTE    Map(can be empty)
- *                 /pojo      Map(field name, field value) (pojo naming is by field.getName())
- *
- * </pre>
+ * <p>We use PojoCache for our internal, replicated data store.
  * <p/>
  *
  * @author Ben Wang
@@ -69,7 +56,7 @@
  * @version $Revision: 58586 $
  */
 class FieldBasedClusteredSession
-   extends JBossCacheClusteredSession
+   extends ClusteredSession<FieldBasedDistributedCacheManager>
 {
    /** The serialVersionUID */
    private static final long serialVersionUID = 8347544395334247623L;
@@ -79,18 +66,27 @@
     */
    protected static final String info = "FieldBasedClusteredSession/1.0";
    
-   protected transient Map attributes_ = Collections.synchronizedMap(new HashMap());
-   protected transient FieldBasedDistributedCacheManager fieldProxy_;
    protected transient boolean needRegion_ = true;
+
    
-   public FieldBasedClusteredSession(JBossCacheManager manager)
+   // ------------------------------------------------------------ Constructors
+   
+   
+   public FieldBasedClusteredSession(ClusteredManager manager)
    {
       super(manager);
    }
 
+   
    // ----------------------------------------------- Overridden Public Methods
 
+   @Override
+   public String getInfo()
+   {
+      return (info);
+   }
 
+
    /**
     * Override the superclass to additionally reset this class' fields.
     * <p>
@@ -99,11 +95,11 @@
     * thorough.
     * </p>
     */
+   @Override
    public void recycle()
    {
       super.recycle();
 
-      attributes_.clear();
       needRegion_ = true;
    }
 
@@ -125,20 +121,6 @@
       needRegion_ = false;
    }
 
-   /**
-    * Return a string representation of this object.
-    */
-   public String toString()
-   {
-
-      StringBuffer sb = new StringBuffer();
-      sb.append("FieldBasedClusteredSession[");
-      sb.append(super.toString());
-      sb.append("]");
-      return (sb.toString());
-
-   }
-
    // The superclass version of processSessionRepl is fine; it will remove
    // the session metadata, and any attribute changes have been picked up
    // for replication as they were made; no need to do anything here
@@ -147,14 +129,16 @@
 //      super.processSessionRepl();
 //   }
 
+   @Override
    public void removeMyself()
    {
       // This is a shortcut to remove session and it's child attributes.
       // Note that there is no need to remove attribute first since caller 
       // will do that already.
-      proxy_.removeSession(realId, true);
+      getDistributedCacheManager().removeSession(getRealId(), true);
    }
 
+   @Override
    public void removeMyselfLocal()
    {
       // Need to evict attribute first before session to clean up everything.
@@ -164,31 +148,12 @@
       // removePojosLocal call here in order to evict the ATTRIBUTE node.  
       // Otherwise empty nodes for the session root and child ATTRIBUTE will 
       // remain in the tree and screw up our list of session names.
-      fieldProxy_.removePojosLocal(realId);
-      fieldProxy_.removeSessionLocal(realId, true);
+      String myRealId = getRealId();
+      getDistributedCacheManager().removePojosLocal(myRealId);
+      getDistributedCacheManager().removeSessionLocal(myRealId, true);
    }
 
-   // ------------------------------------------------ JBoss internal abstract method
-
-
-
-   @Override
-   protected void establishProxy()
-   {
-      if (fieldProxy_ == null)
-      {
-         fieldProxy_ = ((JBossCacheManager) manager).getFieldBasedDistributedCacheManager();
-
-         // still null???
-         if (fieldProxy_ == null)
-         {
-            throw new IllegalStateException("Cache service is null");
-         }
-         
-         // Set the superclass ref as well
-         this.proxy_ = fieldProxy_;
-      }
-   }
+   // --------------------------------------------- Overridden Protected Methods
    
    /**
     * Populate the attributes stored in the distributed store to the local 
@@ -196,13 +161,18 @@
     * remove ourself as an Observer to existing attributes that are no longer
     * in the distributed store.
     */
+   @Override
    protected void populateAttributes()
    {
+      Map<String, Object> attributes = getAttributesInternal();
+      String myRealId = getRealId();
+      
       // Preserve any local attributes that were excluded from replication
-      Map excluded = removeExcludedAttributes(attributes_);
+      Map excluded = removeExcludedAttributes(attributes);
       
-      Set keys = fieldProxy_.getPojoKeys(realId);
-      Set oldKeys = new HashSet(attributes_.keySet());
+      FieldBasedDistributedCacheManager dcm = getDistributedCacheManager();
+      Set keys = dcm.getPojoKeys(myRealId);
+      Set oldKeys = new HashSet(attributes.keySet());
       
       // Since we are going to touch each attribute, might as well
       // check if we have any HttpSessionActivationListener
@@ -216,10 +186,10 @@
          {
             String name = (String) it.next();
             
-            Object newAttrib = fieldProxy_.getPojo(realId, name);
+            Object newAttrib = dcm.getPojo(myRealId, name);
             if (newAttrib != null)
             {
-               attributes_.put(name, newAttrib);
+               attributes.put(name, newAttrib);
                
                // Check if we have a listener
                if (newAttrib instanceof HttpSessionActivationListener)
@@ -228,17 +198,17 @@
             else
             {
                // This shouldn't happen -- if we had a key, newAttrib s/b not null               
-               attributes_.remove(name);                            
+               attributes.remove(name);                            
             }
          }
       }
       
-      hasActivationListener = hasListener ? Boolean.TRUE : Boolean.FALSE;
+      setHasActivationListener(hasListener);
       
       // Cycle through remaining old keys and remove them 
       for (Iterator it = oldKeys.iterator(); it.hasNext(); )
       {
-         attributes_.remove(it.next());
+         attributes.remove(it.next());
       }
       
       // Restore any excluded attributes
@@ -246,10 +216,11 @@
          attributes.putAll(excluded);
    }
  
-   protected Object getJBossInternalAttribute(String name)
+   @Override
+   protected Object getAttributeInternal(String name)
    {
       // Check the local map first.
-      Object result = attributes_.get(name);
+      Object result = getAttributesInternal().get(name);
       
       // NOTE -- we no longer check with the store.  Attributes are only
       // loaded from store during populateAttributes() call at beginning
@@ -270,6 +241,7 @@
     * as "immutable", since as an Observer we will detect any changes
     * to those types.
     */
+   @Override
    protected boolean isMutable(Object attribute)
    {
       boolean pojo = (attribute instanceof Advised);
@@ -277,50 +249,27 @@
       return mutable;
    }
 
-   protected Object removeJBossInternalAttribute(String name, boolean localCall, boolean localOnly)
+   @Override
+   protected Object removeAttributeInternal(String name, boolean localCall, boolean localOnly)
    {
       // Remove it from the underlying store
       if (localCall && !replicationExcludes.contains(name))
       { 
          if (localOnly)         
-            fieldProxy_.removePojoLocal(realId, name);      
+            getDistributedCacheManager().removePojoLocal(getRealId(), name);      
          else
-            fieldProxy_.removePojo(realId, name); 
+            getDistributedCacheManager().removePojo(getRealId(), name); 
          
          sessionAttributesDirty();
       }
-      Object result = attributes_.remove(name);
-      if(result == null)
-      {
-         log.warn("removeJBossInternalAttribute(): null value to remove with key: "+ name);
-         return null;
-      }
-         
-      return result;
+      return getAttributesInternal().remove(name);
    }
-
-   protected Map getJBossInternalAttributes()
-   {
-      return attributes_;
-   }
-
-   protected Set getJBossInternalKeys()
-   {
-      return attributes_.keySet();
-   }
-
+   
    /**
-    * Method inherited from Tomcat. Return zero-length based string if not found.
-    */
-   protected String[] keys()
-   {
-      return ((String[]) getJBossInternalKeys().toArray(EMPTY_ARRAY));
-   }
-
-   /**
     * Overrides the superclass to allow instrumented classes and
     * non-serializable Collections and Maps.
     */
+   @Override
    protected boolean canAttributeBeReplicated(Object attribute)
    {
       return (attribute == null || Util.checkPojoType(attribute));
@@ -334,12 +283,14 @@
     * @param value
     * @return Object
     */
-   protected Object setJBossInternalAttribute(String key, Object value)
+   @Override
+   protected Object setAttributeInternal(String key, Object value)
    {
       Object oldVal = null;
       if (!replicationExcludes.contains(key))
       {   
-         oldVal = fieldProxy_.setPojo(realId, key, value, this);
+         String myRealId = getRealId();
+         oldVal = getDistributedCacheManager().setPojo(myRealId, key, value, this);
    
          if(value != null)
          {
@@ -347,7 +298,7 @@
             if( value instanceof Map || value instanceof Collection)
             {
                // We need to obtain the proxy first.
-               value = fieldProxy_.getPojo(realId, key);
+               value = getDistributedCacheManager().getPojo(myRealId, key);
             }
          }
 
@@ -356,7 +307,7 @@
       }
       
       // Still need to put it in the map to track locally.
-      oldVal = attributes_.put(key, value);
+      oldVal = getAttributesInternal().put(key, value);
       
       return oldVal;
    }

Deleted: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheClusteredSession.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheClusteredSession.java	2008-10-23 18:15:21 UTC (rev 80002)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheClusteredSession.java	2008-10-23 18:55:56 UTC (rev 80003)
@@ -1,196 +0,0 @@
-/*
-* JBoss, Home of Professional Open Source
-* Copyright 2006, JBoss Inc., and individual contributors as indicated
-* by the @authors tag. See the copyright.txt in the distribution for a
-* full listing of individual contributors.
-*
-* This is free software; you can redistribute it and/or modify it
-* under the terms of the GNU Lesser General Public License as
-* published by the Free Software Foundation; either version 2.1 of
-* the License, or (at your option) any later version.
-*
-* This software is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-* Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public
-* License along with this software; if not, write to the Free
-* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
-* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-*/
-
-package org.jboss.web.tomcat.service.session;
-
-import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSession;
-import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager;
-import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause;
-
-/**
- * Common superclass of ClusteredSession types that use JBossCache
- * as their distributed cache.
- * 
- * @author Brian Stansberry 
- * 
- * @version $Revision: 56542 $
- */
-public abstract class JBossCacheClusteredSession 
-   extends ClusteredSession implements DistributableSession
-{
-   /**
-    * Our proxy to the cache.
-    */
-   protected transient DistributedCacheManager proxy_;
-   
-   /**
-    * Create a new JBossCacheClusteredSession.
-    * 
-    * @param manager
-    * @param useJK
-    */
-   public JBossCacheClusteredSession(JBossCacheManager manager)
-   {
-      super(manager, manager.getReplicationTrigger(), manager.getUseJK());
-      int maxUnrep = manager.getMaxUnreplicatedInterval() * 1000;
-      setMaxUnreplicatedInterval(maxUnrep);
-      establishNotificationPolicy();
-      establishProxy();
-   }
-
-   /**
-    * Initialize fields marked as transient after loading this session
-    * from the distributed store
-    *
-    * @param manager the manager for this session
-    */
-   @Override
-   public void initAfterLoad(AbstractJBossManager manager, ClusteredSessionNotificationCause cause)
-   {
-      // Our manager, notification policy and proxy may have been lost 
-      // if we were replicated, so reestablish them
-      setManager(manager);
-      establishNotificationPolicy();
-      establishProxy();
-
-      // Since attribute map may be transient, we may need to populate it 
-      // from the underlying store.
-      populateAttributes();
-      
-      if (cause == ClusteredSessionNotificationCause.ACTIVATION)
-      {
-         this.needsPostReplicateActivation = true;
-      }
-      
-      // Notify all attributes of type HttpSessionActivationListener (SRV 7.7.2)
-      this.activate(cause);
-      
-      // We are no longer outdated vis a vis distributed cache
-      clearOutdated();
-   }
-   
-   public boolean needRegionForSession()
-   {
-      return false;
-   }
-   
-   public boolean hasRegionForSession()
-   {
-      return false;
-   }
-   
-   public void createdRegionForSession()
-   {
-      // ignored
-   }
-   
-   /**
-    * Gets a reference to the JBossCacheService.
-    */
-   protected void establishProxy()
-   {
-      if (proxy_ == null)
-      {
-         proxy_ = ((JBossCacheManager) manager).getDistributedCacheManager();
-
-         // still null???
-         if (proxy_ == null)
-         {
-            throw new RuntimeException("JBossCacheClusteredSession: Cache service is null.");
-         }
-      }
-   }
-   
-   protected abstract void populateAttributes();
-
-   /**
-    * Override the superclass to additionally reset this class' fields.
-    * <p>
-    * <strong>NOTE:</strong> It is not anticipated that this method will be
-    * called on a ClusteredSession, but we are overriding the method to be
-    * thorough.
-    * </p>
-    */
-   public void recycle()
-   {
-      super.recycle();
-      
-      proxy_ = null;
-   }
-
-   /**
-    * Increment our version and place ourself in the cache.
-    */
-   public synchronized void processSessionRepl()
-   {
-      // Replicate the session.
-      if (log.isTraceEnabled())
-      {
-         log.trace("processSessionRepl(): session is dirty. Will increment " +
-                   "version from: " + getVersion() + " and replicate.");
-      }
-      this.incrementVersionFromLocalActivity();
-      proxy_.putSession(realId, this);
-      
-      // Allow subclasses to replicate attributes if needed
-      replicateAttributes();
-      
-      sessionAttributesDirty = false;
-      sessionMetadataDirty = false;
-      
-      updateLastReplicated();
-   }
-   
-   /**
-    * Extension point for subclasses to handle replication of attributes.
-    * This default implementation does nothing.
-    */
-   protected void replicateAttributes()
-   {
-      // no-op
-   }
-
-   /**
-    * Overrides the superclass impl by doing nothing if <code>localCall</code>
-    * is <code>false</code>.  The JBossCacheManager will already be aware of
-    * a remote invalidation and will handle removal itself.
-    */
-   protected void removeFromManager(boolean localCall, boolean localOnly)
-   {
-      if (localCall)
-      {
-         super.removeFromManager(localCall, localOnly);
-      }
-   }
-   
-   
-   protected Object removeAttributeInternal(String name, boolean localCall, boolean localOnly)
-   {
-      return removeJBossInternalAttribute(name, localCall, localOnly);
-   }
-   
-   protected void establishNotificationPolicy()
-   {
-      setNotificationPolicy(((JBossCacheManager)manager).getNotificationPolicy());
-   }
-
-}

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java	2008-10-23 18:15:21 UTC (rev 80002)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java	2008-10-23 18:55:56 UTC (rev 80003)
@@ -53,9 +53,7 @@
 import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager;
 import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManagerFactory;
 import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManagerFactoryFactory;
-import org.jboss.web.tomcat.service.session.distributedcache.spi.FieldBasedDistributedCacheManager;
 import org.jboss.web.tomcat.service.session.distributedcache.spi.LocalDistributableSessionManager;
-
 import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCapability;
 import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause;
 import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationPolicy;
@@ -72,7 +70,7 @@
  */
 public class JBossCacheManager
    extends JBossManager
-   implements JBossCacheManagerMBean, LocalDistributableSessionManager
+   implements JBossCacheManagerMBean, LocalDistributableSessionManager, ClusteredManager
 {
    // --------------------------------------------------------------- Constants
    
@@ -288,7 +286,7 @@
 
                // Notify all session attributes that they get serialized (SRV 7.7.2)
                long begin = System.currentTimeMillis();
-               session.passivate(ClusteredSessionNotificationCause.REPLICATION);
+               session.notifyWillPassivate(ClusteredSessionNotificationCause.REPLICATION);
                long elapsed = System.currentTimeMillis() - begin;
                stats_.updatePassivationStats(realId, elapsed);
 
@@ -331,50 +329,6 @@
       add((ClusteredSession) session, false); // wait to replicate until req end
    }
 
-   /**
-    * Adds the given session to the collection of those being managed by this
-    * Manager.
-    *
-    * @param session   the session. Cannot be <code>null</code>.
-    * @param replicate whether the session should be replicated
-    *
-    * @throws NullPointerException if <code>session</code> is <code>null</code>.
-    */
-   private void add(ClusteredSession session, boolean replicate)
-   {
-      // TODO -- why are we doing this check? The request checks session 
-      // validity and will expire the session; this seems redundant
-      if (!session.isValid())
-      {
-         // Not an error; this can happen if a failover request pulls in an
-         // outdated session from the distributed cache (see TODO above)
-         log_.debug("Cannot add session with id=" + session.getIdInternal() +
-                    " because it is invalid");
-         return;
-      }
-
-      String realId = session.getRealId();
-      Object existing = sessions_.put(realId, session);
-      unloadedSessions_.remove(realId);
-
-      if (!session.equals(existing))
-      {
-         if (replicate)
-         {
-            storeSession(session);
-         }
-
-         // Update counters
-         calcActiveSessions();
-         
-         if (trace_)
-         {
-            log_.trace("Session with id=" + session.getIdInternal() + " added. " +
-                       "Current active sessions " + localActiveCounter_.get());
-         }
-      }
-   }
-
    // Satisfy the Manager interface.  Internally we use
    // createEmptyClusteredSession to avoid a cast
    public Session createEmptySession()
@@ -532,7 +486,7 @@
          // replication, we need to make an offsetting activate() call
          if (session.getNeedsPostReplicateActivation())
          {
-            session.activate(ClusteredSessionNotificationCause.REPLICATION);
+            session.notifyDidActivate(ClusteredSessionNotificationCause.REPLICATION);
          }
       }
 
@@ -625,6 +579,38 @@
       }
    }
 
+   // -------------------------------------------------------  ClusteredManager
+
+   /**
+    * Gets the <code>JBossCacheService</code> through which we interact
+    * with the <code>Cache</code>.
+    */
+   public DistributedCacheManager getDistributedCacheManager()
+   {
+      return proxy_;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public int getMaxUnreplicatedInterval()
+   {
+      return maxUnreplicatedInterval_;
+   }
+
+   public ClusteredSessionNotificationPolicy getNotificationPolicy()
+   {
+      return notificationPolicy_;
+   }    
+   
+   /**
+    * {@inheritDoc}
+    */
+   public ReplicationTrigger getReplicationTrigger()
+   {
+      return this.replicationTrigger_;
+   }
+
    // --------------------------------------------------------------- Lifecycle
    
    /**
@@ -779,14 +765,6 @@
    /**
     * {@inheritDoc}
     */
-   public int getMaxUnreplicatedInterval()
-   {
-      return maxUnreplicatedInterval_;
-   }
-
-   /**
-    * {@inheritDoc}
-    */
    public void setMaxUnreplicatedInterval(int maxUnreplicatedInterval)
    {
       this.maxUnreplicatedInterval_ = maxUnreplicatedInterval;
@@ -822,14 +800,6 @@
    public ReplicationGranularity getReplicationGranularity()
    {
       return replicationGranularity_;
-   }    
-   
-   /**
-    * {@inheritDoc}
-    */
-   public ReplicationTrigger getReplicationTrigger()
-   {
-      return this.replicationTrigger_;
    }
 
    /**
@@ -938,15 +908,19 @@
          boolean localOnly = true; // Don't pass attr removals to cache
          
          // Ensure the correct TCL is in place
-         ClassLoader prevTcl = Thread.currentThread().getContextClassLoader();
+         ContextClassLoaderSwitcher.SwitchContext switcher = null;
          try
          {
-            Thread.currentThread().setContextClassLoader(tcl_);
+            switcher = getContextClassLoaderSwitcher().getSwitchContext();
+            switcher.setClassLoader(tcl_);
             session.expire(notify, localCall, localOnly, ClusteredSessionNotificationCause.INVALIDATE);
          }
          finally
          {
-            Thread.currentThread().setContextClassLoader(prevTcl);
+            if (switcher != null)
+            {
+               switcher.reset();
+            }
          }
 
          // Remove any stats for this session
@@ -1151,40 +1125,6 @@
       this.replicationFieldBatchMode_ = Boolean.valueOf(replicationFieldBatchMode);
    }
 
-   // -------------------------------------------------------------  Properties
-   
-   /**
-    * Gets the <code>FieldBasedJBossCacheService</code> through which we interact
-    * with the PojoCache.
-    * 
-    * @throws IllegalStateException if we are not using field based replication
-    */
-   public FieldBasedDistributedCacheManager getFieldBasedDistributedCacheManager()
-   {
-      if (proxy_ != null && !(proxy_ instanceof FieldBasedDistributedCacheManager))
-         throw new IllegalStateException("PojoCache not being used");
-      return (FieldBasedDistributedCacheManager) proxy_;
-   }
-
-   /**
-    * Gets the <code>JBossCacheService</code> through which we interact
-    * with the <code>Cache</code>.
-    */
-   public DistributedCacheManager getDistributedCacheManager()
-   {
-      return proxy_;
-   }
-
-   public ClusteredSessionNotificationPolicy getNotificationPolicy()
-   {
-      return notificationPolicy_;
-   }
-
-   public void setNotificationPolicy_(ClusteredSessionNotificationPolicy notificationPolicy_)
-   {
-      this.notificationPolicy_ = notificationPolicy_;
-   }
-
    // --------------------------------------------------------------- Overrides
 
    /**
@@ -1421,10 +1361,10 @@
    
    // ------------------------------------------------------ Session Management
 
-   private JBossCacheClusteredSession createEmptyClusteredSession()
+   private ClusteredSession createEmptyClusteredSession()
    {     
 
-      JBossCacheClusteredSession session = null;
+      ClusteredSession session = null;
       switch (replicationGranularity_)
       {
          case ATTRIBUTE:
@@ -1439,6 +1379,50 @@
       }
       return session;
    }
+
+   /**
+    * Adds the given session to the collection of those being managed by this
+    * Manager.
+    *
+    * @param session   the session. Cannot be <code>null</code>.
+    * @param replicate whether the session should be replicated
+    *
+    * @throws NullPointerException if <code>session</code> is <code>null</code>.
+    */
+   private void add(ClusteredSession session, boolean replicate)
+   {
+      // TODO -- why are we doing this check? The request checks session 
+      // validity and will expire the session; this seems redundant
+      if (!session.isValid())
+      {
+         // Not an error; this can happen if a failover request pulls in an
+         // outdated session from the distributed cache (see TODO above)
+         log_.debug("Cannot add session with id=" + session.getIdInternal() +
+                    " because it is invalid");
+         return;
+      }
+
+      String realId = session.getRealId();
+      Object existing = sessions_.put(realId, session);
+      unloadedSessions_.remove(realId);
+
+      if (!session.equals(existing))
+      {
+         if (replicate)
+         {
+            storeSession(session);
+         }
+
+         // Update counters
+         calcActiveSessions();
+         
+         if (trace_)
+         {
+            log_.trace("Session with id=" + session.getIdInternal() + " added. " +
+                       "Current active sessions " + localActiveCounter_.get());
+         }
+      }
+   }
    
    /**
     * Loads a session from the distributed store.  If an existing session with
@@ -1463,7 +1447,7 @@
       boolean mustAdd = false;
       boolean passivated = false;
       
-      JBossCacheClusteredSession session = (JBossCacheClusteredSession) sessions_.get(realId);
+      ClusteredSession session = sessions_.get(realId);
       
       if (session == null)
       {                 
@@ -1605,7 +1589,7 @@
          // from a tx rollback are done.
          SessionReplicationContext.startCacheActivity();
 
-         session.processSessionRepl();
+         session.processSessionReplication();
       }
       catch (Exception ex)
       {
@@ -1669,7 +1653,7 @@
                // Tell the proxy to ignore cache notifications we are about
                // to generate for this session.
                SessionReplicationContext.startCacheActivity();
-               session.passivate(ClusteredSessionNotificationCause.PASSIVATION);
+               session.notifyWillPassivate(ClusteredSessionNotificationCause.PASSIVATION);
                proxy_.evictSession(realId);
                sessionPassivated();
             }

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/LocalStrings.properties
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/LocalStrings.properties	2008-10-23 18:15:21 UTC (rev 80002)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/LocalStrings.properties	2008-10-23 18:55:56 UTC (rev 80003)
@@ -1,9 +1,11 @@
 clusteredSession.attributeEvent=Session attribute event listener threw exception
+clusteredSession.bindingEvent=Session binding event listener threw exception
 clusteredSession.invalidate.ise=invalidate: Session already invalidated
 clusteredSession.isNew.ise=isNew: Session already invalidated
 clusteredSession.getAttribute.ise=getAttribute: Session already invalidated
 clusteredSession.getAttributeNames.ise=getAttributeNames: Session already invalidated
 clusteredSession.getCreationTime.ise=getCreationTime: Session already invalidated
+clusteredSession.getLastAccessedTime.ise=getLastAccessedTime: Session already invalidated
 clusteredSession.getMaxInactiveInterval.ise=getMaxInactiveInterval: Session already invalidated
 clusteredSession.getValueNames.ise=getAttributeNames: Session already invalidated
 clusteredSession.notSerializable=Cannot serialize session attribute {0} for session {1}

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/LocalStrings_ja.properties
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/LocalStrings_ja.properties	2008-10-23 18:15:21 UTC (rev 80002)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/LocalStrings_ja.properties	2008-10-23 18:55:56 UTC (rev 80003)
@@ -1,9 +1,11 @@
 clusteredSession.attributeEvent=\u30bb\u30c3\u30b7\u30e7\u30f3\u5c5e\u6027\u30a4\u30d9\u30f3\u30c8\u30ea\u30b9\u30ca\u304c\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+clusteredSession.bindingEvent=\u30bb\u30c3\u30b7\u30e7\u30f3\u30d0\u30a4\u30f3\u30c7\u30a3\u30f3\u30b0\u30a4\u30d9\u30f3\u30c8\u30ea\u30b9\u30ca\u304c\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
 clusteredSession.invalidate.ise=invalidate: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
 clusteredSession.isNew.ise=isNew: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
 clusteredSession.getAttribute.ise=getAttribute: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
 clusteredSession.getAttributeNames.ise=getAttributeNames: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
 clusteredSession.getCreationTime.ise=getCreationTime: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+clusteredSession.getLastAccessedTime.ise=getLastAccessedTime: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
 clusteredSession.getMaxInactiveInterval.ise=getMaxInactiveInterval: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
 clusteredSession.getValueNames.ise=getAttributeNames: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u3059\u3067\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
 clusteredSession.notSerializable=\u30bb\u30c3\u30b7\u30e7\u30f3 {1} \u306e\u305f\u3081\u306b\u30bb\u30c3\u30b7\u30e7\u30f3\u5c5e\u6027 {0} \u3092\u30b7\u30ea\u30a2\u30e9\u30a4\u30ba\u3067\u304d\u307e\u305b\u3093

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/SessionBasedClusteredSession.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/SessionBasedClusteredSession.java	2008-10-23 18:15:21 UTC (rev 80002)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/SessionBasedClusteredSession.java	2008-10-23 18:55:56 UTC (rev 80003)
@@ -26,20 +26,13 @@
 
 import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionMetadata;
 import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionTimestamp;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager;
 
 
 /**
- * Implementation of a clustered session for the JBossCacheManager. The replication granularity
- * level is session based; that is, we replicate per whole session object.
- * We use JBossCache for our internal replicated data store.
- * The internal structure in JBossCache is as follows:
- * <pre>
- * /JSESSION
- *    /hostname
- *       /web_app_path    (path + session id is unique)
- *          /id    Map(id, session)
- *                    (VERSION_KEY, version)  // Used for version tracking. version is an Integer.
- * </pre>
+ * Implementation of a ClusteredSession where the replication granularity level 
+ * is session based; that is, we replicate the entire attribute map whenever a 
+ * request makes any attribute dirty.
  * <p/>
  * Note that the isolation level of the cache dictates the
  * concurrency behavior.</p>
@@ -50,7 +43,7 @@
  * @version $Revision: 56542 $
  */
 class SessionBasedClusteredSession
-   extends JBossCacheClusteredSession
+   extends ClusteredSession<DistributedCacheManager>
 {
    static final long serialVersionUID = 3200976125245487256L;
 
@@ -59,49 +52,79 @@
     */
    protected static final String info = "SessionBasedClusteredSession/1.0";
 
-   public SessionBasedClusteredSession(JBossCacheManager manager)
+   // ----------------------------------------------------------- Constructors
+
+   
+   public SessionBasedClusteredSession(ClusteredManager manager)
    {
       super(manager);
    }
 
+   
    // ---------------------------------------------- Overridden Public Methods
 
-   /**
-    * Return a string representation of this object.
-    */
-   public String toString()
+   @Override
+   public String getInfo()
    {
-      StringBuffer sb = new StringBuffer();
-      sb.append("SessionBasedClusteredSession[");
-      sb.append(super.toString());
-      sb.append("]");
-      return (sb.toString());
-
+      return (info);
    }
 
+   @Override
    public void removeMyself()
    {
-      proxy_.removeSession(realId, false);
+      getDistributedCacheManager().removeSession(getRealId(), false);
    }
 
+   @Override
    public void removeMyselfLocal()
    {
-      proxy_.removeSessionLocal(realId, false);
+      getDistributedCacheManager().removeSessionLocal(getRealId(), false);
    }
+   
+   @Override
+   public Map<String, Object> getSessionAttributeMap()
+   {
+      Map attrs = new HashMap(getAttributesInternal());
+      removeExcludedAttributes(attrs);
+      return attrs;
+   }
 
-   // ----------------------------------------------HttpSession Public Methods
+   @Override
+   public void update(Integer version, DistributableSessionTimestamp timestamp, 
+         DistributableSessionMetadata metadata, Map updatedAttrs)
+   {
+      // A new session may not have sent over any attributes
+      if (updatedAttrs == null)
+      {
+         updatedAttrs = new HashMap();
+      }
+
+      super.update(version, timestamp, metadata, updatedAttrs);
+      
+      Map existing = getAttributesInternal();
+      Map excluded = removeExcludedAttributes(existing);
+      if (excluded != null)
+         updatedAttrs.putAll(excluded);
+      existing.clear();
+      
+      existing.putAll(updatedAttrs);
+   }
+
+   // -------------------------------------------- Overridden Protected Methods
    
    /**
     * Does nothing -- all attributes are populated already
     */
+   @Override
    protected void populateAttributes()
    {
       // no-op
    }
 
-   protected Object getJBossInternalAttribute(String name)
+   @Override
+   protected Object getAttributeInternal(String name)
    {
-      Object result = attributes.get(name);
+      Object result = getAttributesInternal().get(name);
 
       // Do dirty check even if result is null, as w/ SET_AND_GET null
       // still makes us dirty (ensures timely replication w/o using ACCESS)
@@ -114,50 +137,21 @@
 
    }
 
-   protected Object removeJBossInternalAttribute(String name, 
-                                                 boolean localCall, 
-                                                 boolean localOnly)
+   @Override
+   protected Object removeAttributeInternal(String name, 
+                                            boolean localCall, 
+                                            boolean localOnly)
    {
       if (localCall)
          sessionAttributesDirty();
-      return attributes.remove(name);
+      return getAttributesInternal().remove(name);
    }
 
-   protected Map getJBossInternalAttributes()
+   @Override
+   protected Object setAttributeInternal(String name, Object value)
    {
-      return attributes;
-   }
-
-   protected Object setJBossInternalAttribute(String name, Object value)
-   {
       sessionAttributesDirty();
-      return attributes.put(name, value);
+      return getAttributesInternal().put(name, value);
    }
 
-   @Override
-   public Map<String, Object> getSessionAttributeMap()
-   {
-      Map attrs = new HashMap(attributes);
-      removeExcludedAttributes(attrs);
-      return attrs;
-   }
-
-   public void update(Integer version, DistributableSessionTimestamp timestamp, 
-         DistributableSessionMetadata metadata, Map updatedAttrs)
-   {
-      // A new session may not have sent over any attributes
-      if (updatedAttrs == null)
-      {
-         updatedAttrs = new HashMap();
-      }
-
-      super.update(version, timestamp, metadata, updatedAttrs);
-      
-      Map excluded = removeExcludedAttributes(attributes);
-      if (excluded != null)
-         updatedAttrs.putAll(excluded);
-      
-      this.attributes = updatedAttrs;
-   }
-
 }




More information about the jboss-cvs-commits mailing list