[jboss-cvs] JBossAS SVN: r77439 - 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
Mon Aug 25 11:16:30 EDT 2008


Author: bstansberry at jboss.com
Date: 2008-08-25 11:16:30 -0400 (Mon, 25 Aug 2008)
New Revision: 77439

Modified:
   trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockSession.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/AbstractJBossManager.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/CacheListener.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredSession.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheClusteredSession.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManagerMBean.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossManager.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossManagerMBean.java
Log:
[JBAS-5875] Clustered webapps session manager to use same ObjectName pattern as non-clustered
[JBAS-5876] Expose StandardManager attributes/operations in JBossCacheManagerMBean
[JBAS-5884] Access MBeanServer via Tomcat Registry
General refactor/cleanup of JBossCacheManager/JBossManager. Move stuff to correct class; impose order on completely disorganized methods

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-08-25 15:10:56 UTC (rev 77438)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/web/jvmroute/MockSession.java	2008-08-25 15:16:30 UTC (rev 77439)
@@ -27,6 +27,7 @@
 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.ClusteredSession;
 
@@ -46,7 +47,7 @@
     */
    public MockSession(MockJBossManager manager)
    {
-      super(manager, true);
+      super(manager, ReplicationTrigger.SET_AND_NON_PRIMITIVE_GET, true);
    }
 
    @Override

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/AbstractJBossManager.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/AbstractJBossManager.java	2008-08-25 15:10:56 UTC (rev 77438)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/AbstractJBossManager.java	2008-08-25 15:16:30 UTC (rev 77439)
@@ -23,10 +23,9 @@
 
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.catalina.Session;
 import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
 import org.jboss.metadata.web.jboss.JBossWebMetaData;
-import org.jboss.metadata.web.jboss.ReplicationTrigger;
 
 /** Common interface for the http session replication managers.
  * 
@@ -35,19 +34,16 @@
  */
 public interface AbstractJBossManager extends Manager
 {
-   /** Initialize the manager with the web metadata and 
-    * @param name
-    * @param webMetaData
+   /** 
+    * Initialize the manager with the web metadata
+    *  
+    * @param name the name of the manager, in the form //hostname/context_path
+    * @param webMetaData metadata for the containing web application
     * @throws ClusteringNotSupportedException
     */ 
    public void init(String name, JBossWebMetaData webMetaData)
       throws ClusteringNotSupportedException;
 
-   /** 
-    * Gets the event type(s) that indicate a need to replicate the session.
-    */ 
-   public ReplicationTrigger  getReplicationTrigger();
-
    /**
     * Retrieve the JvmRoute for the enclosing Engine.
     *
@@ -64,7 +60,7 @@
 
    /**
     * Remove the active session locally from the manager without replicating to the cluster. This can be
-    * useful when the session is exipred, for example, where there is not need to propagate the expiration.
+    * useful when the session is expired, for example, where there is not need to propagate the expiration.
     *
     * @param session
     */

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/CacheListener.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/CacheListener.java	2008-08-25 15:10:56 UTC (rev 77438)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/CacheListener.java	2008-08-25 15:16:30 UTC (rev 77439)
@@ -117,29 +117,14 @@
          if(version != null)
          {
             String realId = getIdFromFqn(fqn, isBuddy);
-         
-            ClusteredSession session = manager_.findLocalSession(realId);
-            if (session == null)
-            {
-               String owner = isBuddy ? getBuddyOwner(fqn) : null;
-               // Notify the manager that an unloaded session has been updated
-               manager_.unloadedSessionChanged(realId, owner, 
+            String owner = isBuddy ? getBuddyOwner(fqn) : null;
+            // Notify the manager that a session has been updated
+            boolean updated = manager_.sessionChangedInDistributedCache(realId, owner, 
+                                               version.intValue(), 
                                                (SessionTimestamp) data.get(JBossCacheService.TIMESTAMP_KEY), 
                                                (SessionMetadata) data.get(JBossCacheService.METADATA_KEY));
-            }
-            else if (session.isNewData(version.intValue()))
+            if (!updated && !isBuddy)
             {
-               // Need to invalidate the loaded session
-               session.setOutdatedVersion(version.intValue());
-               if(log_.isTraceEnabled())
-               {
-                  log_.trace("nodeDirty(): session in-memory data is " +
-                             "invalidated with id: " + realId + " and version: " +
-                             version.intValue());
-               }
-            }
-            else if (!isBuddy)
-            {
                log_.warn("Possible concurrency problem: Replicated version id " + 
                          version + " matches in-memory version for session " + realId); 
             }

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-08-25 15:10:56 UTC (rev 77438)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/ClusteredSession.java	2008-08-25 15:16:30 UTC (rev 77439)
@@ -118,7 +118,7 @@
    protected transient SessionMetadata metadata = new SessionMetadata();
    
    /**
-    * The last version that was passed to {@link #setOutdatedVersion} or
+    * 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;
@@ -178,10 +178,10 @@
    protected static StringManager sm =
       StringManager.getManager(ClusteredSession.class.getPackage().getName());
 
-   protected ClusteredSession(AbstractJBossManager manager, boolean useJK)
+   protected ClusteredSession(AbstractJBossManager manager, ReplicationTrigger replicationTrigger, boolean useJK)
    {
       super(manager);
-      invalidationPolicy = manager.getReplicationTrigger();
+      invalidationPolicy = replicationTrigger;
       this.useJK = useJK;
       this.firstAccess = true;
       checkAlwaysReplicateTimestamp();
@@ -198,12 +198,6 @@
       return thisAccessedTime < outdatedTime;
    }
    
-   public void setOutdatedVersion(int version)
-   {
-      this.outdatedVersion = version;
-      outdatedTime = System.currentTimeMillis();
-   }
-   
    public void clearOutdated()
    {
       // Only overwrite the access time if access() hasn't been called
@@ -277,37 +271,37 @@
       return useJK;
    }
 
-   /**
-    * Check to see if the input version number is greater than I am. If it is, 
-    * it means we will need to invalidate the in-memory cache.
-    * @param version
-    * @return
-    */
-   public boolean isNewData(int version)
-   {
-      return (this.version < version);
-   }
-
    public int getVersion()
    {
       return version;
    }
-   
-   public void setVersion(int version)
-   {
-      this.version = version;
-   }
 
    /**
-    * There are couple ways to generate this version number. 
-    * But we will stick with the simple one of incrementing for now.
+    * Increment our version due to local changes.
     * 
     * @return the new version
     */
-   public int incrementVersion()
+   public int incrementVersionFromLocalActivity()
    {
       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)
+   {
+      boolean outdated = this.version < version;
+      if (outdated)
+      {
+         this.outdatedVersion = version;
+         outdatedTime = System.currentTimeMillis();
+      }
+      return outdated;
+   }
 
    /**
     * Overrides the superclass to calculate 
@@ -387,6 +381,16 @@
    {
       return null;
    }
+   
+   /**
+    * Gets the sessions creation time, skipping any validity check.
+    * 
+    * @return the creation time
+    */
+   public long getCreationTimeInternal()
+   {
+      return creationTime;
+   }
 
    /**
     * This is called after loading a session to initialize the transient values.

Modified: 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-08-25 15:10:56 UTC (rev 77438)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheClusteredSession.java	2008-08-25 15:16:30 UTC (rev 77439)
@@ -45,7 +45,7 @@
     */
    public JBossCacheClusteredSession(JBossCacheManager manager)
    {
-      super(manager, manager.getUseJK());
+      super(manager, manager.getReplicationTrigger(), manager.getUseJK());
       int maxUnrep = manager.getMaxUnreplicatedInterval() * 1000;
       setMaxUnreplicatedInterval(maxUnrep);
       establishProxy();
@@ -135,7 +135,7 @@
          log.trace("processSessionRepl(): session is dirty. Will increment " +
                    "version from: " + getVersion() + " and replicate.");
       }
-      this.incrementVersion();
+      this.incrementVersionFromLocalActivity();
       proxy_.putSession(realId, this);
       
       // Allow subclasses to replicate attributes if needed

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-08-25 15:10:56 UTC (rev 77438)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManager.java	2008-08-25 15:16:30 UTC (rev 77439)
@@ -22,7 +22,6 @@
 package org.jboss.web.tomcat.service.session;
 
 import java.security.AccessController;
-import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -32,15 +31,13 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import javax.management.MBeanServer;
 import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
+import javax.transaction.RollbackException;
 import javax.transaction.Status;
 import javax.transaction.TransactionManager;
-import javax.transaction.RollbackException;
 
 import org.apache.catalina.Context;
-import org.apache.catalina.Host;
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.Session;
 import org.apache.catalina.Valve;
@@ -58,7 +55,6 @@
 import org.jboss.metadata.web.jboss.ReplicationGranularity;
 import org.jboss.metadata.web.jboss.ReplicationTrigger;
 import org.jboss.metadata.web.jboss.SnapshotMode;
-import org.jboss.mx.util.MBeanServerLocator;
 import org.jboss.util.loading.ContextClassLoaderSwitcher;
 import org.jboss.web.tomcat.service.session.ClusteredSession.SessionMetadata;
 import org.jboss.web.tomcat.service.session.ClusteredSession.SessionTimestamp;
@@ -76,61 +72,55 @@
    extends JBossManager
    implements JBossCacheManagerMBean
 {
-
+   // --------------------------------------------------------------- Constants
+   
    /**
     * Informational name for this Catalina component
     */
-   static final String info_ = "JBossCacheManager/1.0";
+   private static final String info_ = "JBossCacheManager/1.0";
 
-   // -- Class attributes ---------------------------------
+   // ------------------------------------------------------------------ Fields
 
-   /**
-    * The transaction manager.
-    */
+   /** The transaction manager. */
    private TransactionManager tm;
 
-   /**
-    * Proxy-object for the JBossCacheService
-    */
+   /** Proxy-object for the JBossCache */
    private JBossCacheService proxy_;
 
-   /**
-    * Id/timestamp of sessions in cache that we haven't loaded
-    */
+   /** Id/timestamp of sessions in distributedcache that we haven't loaded locally*/
    private Map<String, OwnedSessionUpdate> unloadedSessions_ = 
          new ConcurrentHashMap<String, OwnedSessionUpdate>();
    
+   /** Number of passivated sessions */
    private AtomicInteger passivatedCount_ = new AtomicInteger();
-   private int maxPassivatedCount_;
    
-   private int maxLocalActiveCounter_;
+   /** Maximum number of concurrently passivated sessions */
+   private AtomicInteger maxPassivatedCount_ = new AtomicInteger();
    
+   /** Injected pojo cache */
    private PojoCache pojoCache_;
    
+   /** Injected plain cache */
    private Cache plainCache_;
 
-   /**
-    * If set to true, will add a JvmRouteFilter to the request.
-    */
+   /** If set to true, will add a JvmRouteFilter to the request. */
    private Boolean useJK_;
 
    /** Are we running embedded in JBoss? */
    private boolean embedded_ = false;
 
-   /** Our JMX Server */
-   private MBeanServer mserver_ = null;
-
    /** Our ClusteredSessionValve's snapshot mode. */
    private SnapshotMode snapshotMode_ = null;
 
    /** Our ClusteredSessionValve's snapshot interval. */
    private int snapshotInterval_ = 0;
    
-   /**
-    * Replication granularity.
-    */
+   /** Replication granularity. */
    private ReplicationGranularity replicationGranularity_;
 
+   /** Policy to determine if a session is dirty */
+   private ReplicationTrigger replicationTrigger_;
+
    /**
     * Whether we use batch mode replication for field level granularity.
     * We store this in a Boolean rather than a primitive so JBossCacheCluster
@@ -141,17 +131,13 @@
    /** Class loader for this web app. */
    private ClassLoader tcl_;
    
-   /**
-    * The snapshot manager we are using.
-    */
+   /** The snapshot manager we are using. */
    private SnapshotManager snapshotManager_;
    
-   /**
-    * The name of our cache's configuration
-    */
+   /** The name of our cache's configuration */
    private String cacheConfigName_;
    
-   /** Did we get our cache from a cache manager */
+   /** Did we get our cache from a cache manager? */
    private boolean cacheFromCacheManager_;
    
    private int maxUnreplicatedInterval_ = -1;
@@ -175,22 +161,28 @@
       this.plainCache_ = cache;
    }
 
+   // ---------------------------------------------------- AbstractJBossManager
+
    /**
-    * Initializes this Manager when running in embedded mode.
+    * {@inheritDoc}
     * <p>
     * <strong>NOTE:</strong> This method should not be called when
     * running unembedded.
     * </p>
     */
+   @Override
    public void init(String name, JBossWebMetaData webMetaData)
       throws ClusteringNotSupportedException
    {
       super.init(name, webMetaData);
       
       ReplicationConfig repCfg = webMetaData.getReplicationConfig();
+      if (repCfg != null)
+      {         
+         replicationTrigger_ = repCfg.getReplicationTrigger();
+         replicationGranularity_ = repCfg.getReplicationGranularity();
+      }       
       
-      replicationGranularity_ = repCfg.getReplicationGranularity();
-      
       // Only configure JK usage if it was explicitly set; otherwise
       // wait until we're starting when we can check for a jvmRoute
       // in our containing Engine
@@ -213,6 +205,7 @@
       }
       
       log_.debug("init(): replicationGranularity_ is " + replicationGranularity_ +
+         " and replicationTrigger is " + replicationTrigger_ +
          " and replicationFieldBatchMode is " + replicationFieldBatchMode_ +
          " and useJK is " + useJK_ +
          " and snapshotMode is " + snapshotMode_ +
@@ -232,598 +225,54 @@
    }
 
    /**
-    * Accesses the underlying cache and creates the proxy
-    * 
-    * @throws ClusteringNotSupportedException
+    * {@inheritDoc}
+    * <p>
+    * Removes the session from this Manager's collection of actively managed
+    * sessions.  Also removes the session from this server's copy of the
+    * distributed cache (but does not remove it from other servers'
+    * distributed cache).
+    * </p>
     */
-   private void initCacheProxy() throws ClusteringNotSupportedException
+   public void removeLocal(Session session)
    {
-      // We are likely going to cause creation and start of a cache here;
-      // we don't want to leak the TCCL to cache/jgroups threads,
-      // so we switch it to our classloader
-      ContextClassLoaderSwitcher switcher = (ContextClassLoaderSwitcher) AccessController.doPrivileged(ContextClassLoaderSwitcher.INSTANTIATOR);
-      ContextClassLoaderSwitcher.SwitchContext switchContext = switcher.getSwitchContext(getClass().getClassLoader());
-      try
+      ClusteredSession clusterSess = (ClusteredSession) session;
+      synchronized (clusterSess)
       {
-         if (ReplicationGranularity.FIELD == replicationGranularity_)
-         {
-            PojoCache pojoC = getPojoCache();
-            if (pojoC == null)
-            {
-               pojoC = Util.findPojoCache(cacheConfigName_);
-               
-               cacheFromCacheManager_ = true;
-               
-               if (pojoC.getCache().getCacheStatus() != CacheStatus.STARTED)
-                  pojoC.getCache().start();
-            }
-            proxy_ = new FieldBasedJBossCacheService(pojoC);
-         }
-         else
-         {
-            Cache pc = getPlainCache();
-            if (pc == null)
-            {
-               pc = Util.findPlainCache(cacheConfigName_);
-               
-               cacheFromCacheManager_ = true;
-               
-               if (pc.getCacheStatus() != CacheStatus.STARTED)
-                  pc.start();
-            }
-            proxy_ = new JBossCacheService(pc);
-         }
-      }
-      finally
-      {
-         // Restore the TCCL
-         switchContext.reset();
-      }
-   }
+         String realId = clusterSess.getRealId();
+         if (realId == null) return;
 
-   // -------------------------------------------------------------  Properties
-
-   /**
-    * Gets the <code>FieldBasedJBossCacheService</code> through which we interact
-    * with the PojoCache.
-    * 
-    * @throws IllegalStateException if we are not using field based replication
-    */
-   public FieldBasedJBossCacheService getPojoCacheService()
-   {
-      if (proxy_ != null && !(proxy_ instanceof FieldBasedJBossCacheService))
-         throw new IllegalStateException("PojoCache not being used");
-      return (FieldBasedJBossCacheService) proxy_;
-   }
-
-   /**
-    * Gets the <code>JBossCacheService</code> through which we interact
-    * with the <code>Cache</code>.
-    */
-   public JBossCacheService getCacheService()
-   {
-      return proxy_;
-   }
-
-   /**
-    * Gets when sessions are replicated to the other nodes.
-    * The default value, "instant", synchronously replicates changes
-    * to the other nodes. In this case, the "SnapshotInterval" attribute
-    * is not used.
-    * The "interval" mode, in association with the "SnapshotInterval"
-    * attribute, indicates that Tomcat will only replicate modified
-    * sessions every "SnapshotInterval" miliseconds at most.
-    *
-    * @see #getSnapshotInterval()
-    */
-   public SnapshotMode getSnapshotMode()
-   {
-      return snapshotMode_;
-   }
-
-   /**
-    * Sets when sessions are replicated to the other nodes. Valid values are:
-    * <ul>
-    * <li>instant</li>
-    * <li>interval</li>
-    * </ul>
-    */
-   public void setSnapshotMode(SnapshotMode snapshotMode)
-   {
-      this.snapshotMode_ = snapshotMode;
-   }
-
-   /**
-    * Gets how often session changes should be replicated to other nodes.
-    * Only relevant if property {@link #getSnapshotMode() snapshotMode} is
-    * set to <code>interval</code>.
-    *
-    * @return the number of milliseconds between session replications.
-    */
-   public int getSnapshotInterval()
-   {
-      return snapshotInterval_;
-   }
-
-   /**
-    * Sets how often session changes should be replicated to other nodes.
-    *
-    * @param snapshotInterval the number of milliseconds between
-    *                         session replications.
-    */
-   public void setSnapshotInterval(int snapshotInterval)
-   {
-      this.snapshotInterval_ = snapshotInterval;
-   }
-
-   /**
-    * Gets whether the <code>Engine</code> in which we are running
-    * uses <code>mod_jk</code>.
-    */
-   public boolean getUseJK()
-   {
-      return useJK_ == null ? false : useJK_.booleanValue();
-   }
-
-   /**
-    * Sets whether the <code>Engine</code> in which we are running
-    * uses <code>mod_jk</code>.
-    */
-   public void setUseJK(boolean useJK)
-   {
-      this.useJK_ = Boolean.valueOf(useJK);
-   }
-
-   /**
-    * Returns the replication granularity.
-    *
-    *  @see JBossWebMetaData#REPLICATION_GRANULARITY_ATTRIBUTE
-    *  @see JBossWebMetaData#REPLICATION_GRANULARITY_FIELD
-    *  @see JBossWebMetaData#REPLICATION_GRANULARITY_SESSION
-    */
-   public ReplicationGranularity getReplicationGranularity()
-   {
-      return replicationGranularity_;
-   }
-
-   /**
-    * Sets the granularity of session data replicated across the cluster.
-    * Valid values are:
-    * <ul>
-    * <li>SESSION</li>
-    * <li>ATTRIBUTE</li>
-    * <li>FIELD</li>
-    * </ul>
-    */
-   public void setReplicationGranularity(ReplicationGranularity granularity)
-   {
-      this.replicationGranularity_ = granularity;
-   }
-
-   /**
-    * Returns the replication granularity.
-    *
-    *  @see JBossWebMetaData#REPLICATION_GRANULARITY_ATTRIBUTE
-    *  @see JBossWebMetaData#REPLICATION_GRANULARITY_FIELD
-    *  @see JBossWebMetaData#REPLICATION_GRANULARITY_SESSION
-    */
-   public String getReplicationGranularityString()
-   {
-      return replicationGranularity_ == null ? null : replicationGranularity_.toString();
-   }
-
-   /**
-    * Sets the granularity of session data replicated across the cluster.
-    * Valid values are:
-    * <ul>
-    * <li>SESSION</li>
-    * <li>ATTRIBUTE</li>
-    * <li>FIELD</li>
-    * </ul>
-    */
-   public void setReplicationGranularityString(String granularity)
-   {
-      setReplicationGranularity(granularity == null ? null : 
-                  ReplicationGranularity.fromString(granularity.toUpperCase()));
-   }     
-
-   public String getReplicationTriggerString()
-   {      
-      return replicationTrigger_ == null ? null : replicationTrigger_.toString();
-   }
-
-   public void setReplicationTriggerString(String trigger)
-   {      
-      setReplicationTrigger(trigger == null ? null : ReplicationTrigger.fromString(trigger.toUpperCase()));
-   }
-
-   /**
-    * Gets whether, if replication granularity is set to <code>FIELD</code>,
-    * replication should be done in batch mode.  Ignored if field-level
-    * granularity is not used.
-    */
-   public Boolean isReplicationFieldBatchMode()
-   {
-      return replicationFieldBatchMode_;
-   }
-
-   /**
-    * Sets whether, if replication granularity is set to <code>FIELD</code>,
-    * replication should be done in batch mode.  Ignored if field-level
-    * granularity is not used.
-    */
-   public void setReplicationFieldBatchMode(boolean replicationFieldBatchMode)
-   {
-      this.replicationFieldBatchMode_ = Boolean.valueOf(replicationFieldBatchMode);
-   }
-
-   public int getMaxUnreplicatedInterval()
-   {
-      return maxUnreplicatedInterval_;
-   }
-
-   public void setMaxUnreplicatedInterval(int maxUnreplicatedInterval)
-   {
-      this.maxUnreplicatedInterval_ = maxUnreplicatedInterval;
-   }  
-   
-
-   // JBossCacheManagerMBean-methods -------------------------------------
-
-   public void expireSession(String sessionId)
-   {
-      Session session = findSession(sessionId);
-      if (session != null)
-         session.expire();
-   }
-
-   public String getLastAccessedTime(String sessionId)
-   {
-      Session session = findSession(sessionId);
-      if(session == null) {
-         if (log_.isTraceEnabled())
+         if (trace_)
          {
-            log_.trace("getLastAccessedTime(): Session " + sessionId + 
-                       " not found");
+            log_.trace("Removing session from local store with id: " + realId);
          }
-         return "";
-      }
-     return new Date(session.getLastAccessedTime()).toString();
-   }
 
-   public Object getSessionAttribute(String sessionId, String key)
-   {
-      ClusteredSession session = (ClusteredSession) findSession(sessionId);
-      return (session == null) ? null : session.getAttribute(key);
-   }
-
-   public String getSessionAttributeString(String sessionId, String key)
-   {
-      Object attr = getSessionAttribute(sessionId, key);
-      return (attr == null) ? null : attr.toString();
-   }
-   
-   public String listLocalSessionIds()
-   {
-      return reportSessionIds(sessions_.keySet());
-   }
-   
-   public String listSessionIds()
-   {
-      Set ids = new HashSet(sessions_.keySet());
-      ids.addAll(unloadedSessions_.keySet());
-      return reportSessionIds(ids);
-   }
-   
-   private String reportSessionIds(Set ids)
-   {
-      StringBuffer sb = new StringBuffer();
-      boolean added = false;
-      for (Iterator it = ids.iterator(); it.hasNext(); )
-      {
-         if (added)
-         {
-            sb.append(',');
+         try {
+            // Ignore any cache notifications that our own work generates
+            SessionReplicationContext.startCacheActivity();
+            clusterSess.removeMyselfLocal();
          }
-         else
+         finally
          {
-            added = true;
-         }
-         
-         sb.append(it.next());
-      }
-      return sb.toString();
-   }
+            SessionReplicationContext.finishCacheActivity();
+            
+            // We don't want to replicate this session at the end
+            // of the request; the removal process took care of that
+            SessionReplicationContext.sessionExpired(clusterSess, realId, snapshotManager_);
+            
+            sessions_.remove(realId);
+            stats_.removeStats(realId);
 
-   public long getLocalActiveSessionCount()
-   {
-      return activeCounter_;
-   }
-
-   public long getMaxLocalActiveSessionCount()
-   {
-      return super.getMaxActiveSessionCount();
-   }
-
-   public long getMaxPassivatedSessionCount()
-   {
-      return 0;
-   }
-
-   public long getPassivatedSessionCount()
-   {
-      return passivatedCount_.get();
-   }
-   
-   @Override
-   public int getActiveSessions()
-   {
-      return calcActiveSessions();
-   }
-
-   @Override
-   public long getActiveSessionCount()
-   {
-      return calcActiveSessions();
-   }
-
-   public long getPassivationMaxIdleTime()
-   {
-      return passivationMaxIdleTime_;
-   }
-
-   public long getPassivationMinIdleTime()
-   {
-      return passivationMinIdleTime_;
-   }
-   
-
-   // Manager-methods -------------------------------------
-
-   /**
-    * Start this Manager
-    *
-    * @throws org.apache.catalina.LifecycleException
-    *
-    */
-   public void start() throws LifecycleException
-   {
-      if (embedded_)
-      {
-         startEmbedded();
-      }
-      else
-      {
-         startUnembedded();
-      }
-      
-      log_.debug("JBossCacheManager for " + getContainer().getName() + " started");
-   }
-
-   public void stop() throws LifecycleException
-   {
-      if (!started_)
-      {
-         throw new IllegalStateException("Manager not started");
-      }
-      
-      log_.debug("Stopping");
-      
-      // Block for any ongoing backgroundProcess, then disable
-      synchronized (backgroundProcessAllowed)
-      {
-         backgroundProcessAllowed.set(false);
-      }
-      
-      resetStats();
-      
-      // Notify our interested LifecycleListeners
-      lifecycle_.fireLifecycleEvent(BEFORE_STOP_EVENT, this);
-      
-      clearSessions();
-      
-      // Don't leak the classloader
-      tcl_ = null;
-      
-      proxy_.stop();
-      proxy_ = null;
-      
-      tm = null;
-      
-      if (cacheFromCacheManager_)
-      {
-         // Let the manager know we are done with the cache
-         releaseCacheToManager();
-      }
-      
-      snapshotManager_.stop();
-      
-      // Clean up maps
-      sessions_.clear();
-      unloadedSessions_.clear();
-      
-      passivatedCount_.set(0);
-      
-      started_ = false;
-      
-      // Notify our interested LifecycleListeners
-      lifecycle_.fireLifecycleEvent(AFTER_STOP_EVENT, this);
-      
-      try
-      {
-         unregisterMBeans();
-      }
-      catch (Exception e)
-      {
-         log_.error("Could not unregister ManagerMBean from MBeanServer", e);
-      }
-   }
-
-   /**
-    * Clear the underlying cache store.
-    */
-   protected void clearSessions()
-   {
-      boolean passivation = isPassivationEnabled();
-      // First, the sessions we have actively loaded
-      ClusteredSession[] sessions = findLocalSessions();
-      for(int i=0; i < sessions.length; i++)
-      {
-         ClusteredSession ses = sessions[i];
-         // JBCLUSTER-15
-         // if session passivation is enabled, passivate sessions instead of expiring them which means
-         // they'll be available to the manager for activation after a restart. 
-         if (log_.isTraceEnabled())
-         {
-             log_.trace("clearSessions(): clear session by expiring or passivating: " + ses);
+            // Compute how long this session has been alive, and update
+            // our statistics accordingly
+            int timeAlive = (int) ((System.currentTimeMillis() - clusterSess.getCreationTimeInternal())/1000);
+            sessionExpired(timeAlive);
          }
-         boolean notify = true;
-         boolean localCall = true;
-         boolean localOnly = true;
-         try
-         {
-            if(passivation && ses.isValid())
-            {               
-               processSessionPassivation(ses.getRealId());
-            }
-            else
-            {
-               ses.expire(notify, localCall, localOnly);               
-            }
-         }
-         catch (Throwable t)
-         {
-            log_.warn("clearSessions(): Caught exception expiring or passivating session " +
-                     ses.getIdInternal(), t);
-         }
-         finally
-         {
-            // Guard against leaking memory if anything is holding a
-            // ref to the session by clearing its internal state
-            ses.recycle();
-         }
-      }      
-      
-      String action = passivation ? "evicting" : "removing";
-      Set<Map.Entry<String, OwnedSessionUpdate>> unloaded = 
-               unloadedSessions_.entrySet();
-      for (Iterator<Map.Entry<String, OwnedSessionUpdate>> it = unloaded.iterator(); it.hasNext();)
-      {
-         Map.Entry<String, OwnedSessionUpdate> entry = it.next();
-         String realId = entry.getKey();         
-         try
-         {
-            if (passivation)
-            {
-               OwnedSessionUpdate osu = entry.getValue();
-               // Ignore the marker entries for our passivated sessions
-               if (!osu.passivated)
-               {
-                  proxy_.evictSession(realId, osu.owner);
-               }
-            }
-            else
-            {
-               proxy_.removeSessionLocal(realId, false);           
-            }
-         }
-         catch (Exception e)
-         {
-            // Not as big a problem; we don't own the session
-            log_.debug("Problem " + action + " session " + realId + " -- " + e);
-         }
-         it.remove(); 
       }
    }
 
    /**
-    * Create a new session with a generated id.
+    * {@inheritDoc}
     */
-   public Session createSession()
-   {
-      return createSession(null);
-   }
-
-   /**
-    * Create a new session.
-    *
-    * @param sessionId the id to use, or <code>null</code> if we should
-    *                  generate a new id
-    *
-    * @return the session
-    *
-    * @throws IllegalStateException if the current number of active sessions
-    *         exceeds the maximum number allowed
-    */
-   public Session createSession(String sessionId)
-   {      
-      // First check if we've reached the max allowed sessions, 
-      // then try to expire/passivate sessions to free memory
-      // maxActive_ -1 is unlimited
-      // We check here for maxActive instead of in add().  add() gets called
-      // when we load an already existing session from the distributed cache
-      // (e.g. in a failover) and we don't want to fail in that situation.
-
-      if(maxActive_ != -1 && calcActiveSessions() >= maxActive_)
-      {
-         if (log_.isTraceEnabled())
-         {
-            log_.trace("createSession(): active sessions = " + calcActiveSessions() +
-                       " and max allowed sessions = " + maxActive_);
-         }
-         
-         processExpires();
-         
-         if (calcActiveSessions() >= maxActive_)
-         {
-            // Exceeds limit. We need to reject it.
-            rejectedCounter_++;
-            // Catalina api does not specify what happens
-            // but we will throw a runtime exception for now.
-            String msgEnd = (sessionId == null) ? "" : " id " + sessionId;
-            throw new IllegalStateException("createSession(): number of " +
-                   "active sessions exceeds the maximum limit: " +
-                   maxActive_ + " when trying to create session" + msgEnd);
-         }
-      }
-
-      ClusteredSession session = createEmptyClusteredSession();
-
-      session.setNew(true);
-      session.setCreationTime(System.currentTimeMillis());
-      session.setMaxInactiveInterval(this.maxInactiveInterval_);
-      session.setValid(true);
-
-      if (sessionId == null)
-      {
-          sessionId = this.getNextId();
-
-          // We are using mod_jk for load balancing. Append the JvmRoute.
-          if (getUseJK())
-          {
-              if (log_.isTraceEnabled())
-              {
-                  log_.trace("createSession(): useJK is true. Will append JvmRoute: " + this.getJvmRoute());
-              }
-              sessionId += "." + this.getJvmRoute();
-          }
-      }
-
-      session.setId(sessionId); // Setting the id leads to a call to add()
-
-      if (log_.isTraceEnabled())
-      {
-         log_.trace("Created a ClusteredSession with id: " + sessionId);
-      }
-
-      createdCounter_++; // the call to add() handles the other counters 
-      
-      // Add this session to the set of those potentially needing replication
-      SessionReplicationContext.bindSession(session, snapshotManager_);
-      
-      return session;
-   }
-
    public boolean storeSession(Session baseSession)
    {
       boolean stored = false;
@@ -833,7 +282,7 @@
 
          synchronized (session)
          {
-            if (log_.isTraceEnabled())
+            if (trace_)
             {
                log_.trace("check to see if needs to store and replicate " +
                           "session with id " + session.getIdInternal());
@@ -857,7 +306,7 @@
                stored = true;
                stats_.updateReplicationStats(realId, elapsed);
             }
-            else if (log_.isTraceEnabled())
+            else if (trace_)
             {
                log_.trace("Session " + session.getIdInternal() + 
                           " did not require replication.");
@@ -868,6 +317,11 @@
       return stored;
    }
 
+   // ----------------------------------------------------------------- Manager
+
+   /**
+    * {@inheritDoc}
+    */
    public void add(Session session)
    {
       if (session == null)
@@ -917,12 +371,13 @@
             storeSession(session);
          }
 
+         // Update counters
          calcActiveSessions();
          
-         if (log_.isTraceEnabled())
+         if (trace_)
          {
             log_.trace("Session with id=" + session.getIdInternal() + " added. " +
-                       "Current active sessions " + activeCounter_);
+                       "Current active sessions " + localActiveCounter_.get());
          }
       }
    }
@@ -931,27 +386,91 @@
    // createEmptyClusteredSession to avoid a cast
    public Session createEmptySession()
    {
-      log_.trace("Creating an empty ClusteredSession");
+      if (trace_)
+      {
+         log_.trace("Creating an empty ClusteredSession");
+      }
       
       return createEmptyClusteredSession();
    }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public Session createSession()
+   {
+      return createSession(null);
+   }
 
-   private JBossCacheClusteredSession createEmptyClusteredSession()
-   {     
+   /**
+    * {@inheritDoc}
+    */
+   public Session createSession(String sessionId)
+   {      
+      // First check if we've reached the max allowed sessions, 
+      // then try to expire/passivate sessions to free memory
+      // maxActiveAllowed_ -1 is unlimited
+      // We check here for maxActive instead of in add().  add() gets called
+      // when we load an already existing session from the distributed cache
+      // (e.g. in a failover) and we don't want to fail in that situation.
 
-      JBossCacheClusteredSession session = null;
-      switch (replicationGranularity_)
+      if(maxActiveAllowed_ != -1 && calcActiveSessions() >= maxActiveAllowed_)
       {
-         case ATTRIBUTE:
-         session = new AttributeBasedClusteredSession(this);
-            break;
-         case FIELD:
-            session = new FieldBasedClusteredSession(this);
-            break;
-         default:
-            session = new SessionBasedClusteredSession(this);
-            break;
+         if (trace_)
+         {
+            log_.trace("createSession(): active sessions = " + calcActiveSessions() +
+                       " and max allowed sessions = " + maxActiveAllowed_);
+         }
+         
+         processExpirationPassivation();
+         
+         if (calcActiveSessions() >= maxActiveAllowed_)
+         {
+            // Exceeds limit. We need to reject it.
+            rejectedCounter_.incrementAndGet();
+            // Catalina api does not specify what happens
+            // but we will throw a runtime exception for now.
+            String msgEnd = (sessionId == null) ? "" : " id " + sessionId;
+            throw new IllegalStateException("createSession(): number of " +
+                   "active sessions exceeds the maximum limit: " +
+                   maxActiveAllowed_ + " when trying to create session" + msgEnd);
+         }
       }
+
+      ClusteredSession session = createEmptyClusteredSession();
+
+      session.setNew(true);
+      session.setCreationTime(System.currentTimeMillis());
+      session.setMaxInactiveInterval(this.maxInactiveInterval_);
+      session.setValid(true);
+
+      if (sessionId == null)
+      {
+          sessionId = this.getNextId();
+
+          // We are using mod_jk for load balancing. Append the JvmRoute.
+          if (getUseJK())
+          {
+              if (trace_)
+              {
+                  log_.trace("createSession(): useJK is true. Will append JvmRoute: " + this.getJvmRoute());
+              }
+              sessionId += "." + this.getJvmRoute();
+          }
+      }
+
+      session.setId(sessionId); // Setting the id leads to a call to add()
+
+      if (trace_)
+      {
+         log_.trace("Created a ClusteredSession with id: " + sessionId);
+      }
+
+      createdCounter_.incrementAndGet(); // the call to add() handles the other counters 
+      
+      // Add this session to the set of those potentially needing replication
+      SessionReplicationContext.bindSession(session, snapshotManager_);
+      
       return session;
    }
 
@@ -987,7 +506,7 @@
       if (session == null 
             && !SessionReplicationContext.isSessionBoundAndExpired(realId, snapshotManager_))
       {
-         if (log_.isTraceEnabled())
+         if (trace_)
             log_.trace("Checking for session " + realId + " in the distributed cache");
          
          session = loadSession(realId);
@@ -1000,7 +519,7 @@
       }
       else if (session != null && session.isOutdated())
       {
-         if (log_.isTraceEnabled())
+         if (trace_)
             log_.trace("Updating session " + realId + " from the distributed cache");
          
          // Need to update it from the cache
@@ -1039,7 +558,7 @@
          // Make a thread-safe copy of the new id list to work with
          Set ids = new HashSet(unloadedSessions_.keySet());
 
-         if(log_.isTraceEnabled()) {
+         if(trace_) {
             log_.trace("findSessions: loading sessions from distributed cache: " + ids);
          }
 
@@ -1053,37 +572,14 @@
    }
 
    /**
-    * Returns all the sessions that are being actively managed by this manager.
-    * This includes those that were created on this server, those that were
-    * brought into local management by a call to
-    * {@link #findLocalSession(String)} as well as all sessions brought into
-    * local management by a call to {@link #findSessions()}.
+    * {@inheritDoc}
     */
-   public ClusteredSession[] findLocalSessions()
+   public String getInfo()
    {
-      Collection coll = sessions_.values();
-      ClusteredSession[] sess = new ClusteredSession[coll.size()];
-      sess = (ClusteredSession[]) coll.toArray(sess);
-      return sess;
+      return info_;
    }
 
    /**
-    * Returns the given session if it is being actively managed by this manager.
-    * An actively managed session is on that was either created on this server,
-    * brought into local management by a call to
-    * {@link #findLocalSession(String)} or brought into local management by a
-    * call to {@link #findSessions()}.
-    *
-    * @param realId the session id, with any trailing jvmRoute removed.
-    *
-    * @see #getRealId(String)
-    */
-   public ClusteredSession findLocalSession(String realId)
-   {
-      return (ClusteredSession) sessions_.get(realId);
-   }
-
-   /**
     * Removes the session from this Manager's collection of actively managed
     * sessions.  Also removes the session from the distributed cache, both
     * on this server and on all other server to which this one replicates.
@@ -1097,7 +593,7 @@
          if (realId == null)
             return;
 
-         if (log_.isTraceEnabled())
+         if (trace_)
          {
             log_.trace("Removing session from store with id: " + realId);
          }
@@ -1116,278 +612,607 @@
             
             sessions_.remove(realId);
             stats_.removeStats(realId);
-//            activeCounter_--;
+
+            // Compute how long this session has been alive, and update
+            // our statistics accordingly
+            int timeAlive = (int) ((System.currentTimeMillis() - clusterSess.getCreationTimeInternal())/1000);
+            sessionExpired(timeAlive);
          }
       }
    }
 
+   // --------------------------------------------------------------- Lifecycle
+   
    /**
-    * Removes the session from this Manager's collection of actively managed
-    * sessions.  Also removes the session from this server's copy of the
-    * distributed cache (but does not remove it from other servers'
-    * distributed cache).
+    * {@inheritDoc}
     */
-   public void removeLocal(Session session)
+   @Override
+   public void start() throws LifecycleException
    {
-      ClusteredSession clusterSess = (ClusteredSession) session;
-      synchronized (clusterSess)
+      if (embedded_)
       {
-         String realId = clusterSess.getRealId();
-         if (realId == null) return;
+         startEmbedded();
+      }
+      else
+      {
+         startUnembedded();
+      }
+      
+      log_.debug("JBossCacheManager for " + getContainer().getName() + " started");
+   }
 
-         if (log_.isTraceEnabled())
-         {
-            log_.trace("Removing session from local store with id: " + realId);
-         }
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void stop() throws LifecycleException
+   {
+      if (!started_)
+      {
+         throw new IllegalStateException("Manager not started");
+      }
+      
+      log_.debug("Stopping");
+      
+      // Block for any ongoing backgroundProcess, then disable
+      synchronized (backgroundProcessAllowed)
+      {
+         backgroundProcessAllowed.set(false);
+      }
+      
+      resetStats();
+      
+      // Notify our interested LifecycleListeners
+      lifecycle_.fireLifecycleEvent(BEFORE_STOP_EVENT, this);
+      
+      clearSessions();
+      
+      // Don't leak the classloader
+      tcl_ = null;
+      
+      proxy_.stop();
+      proxy_ = null;
+      
+      tm = null;
+      
+      if (cacheFromCacheManager_)
+      {
+         // Let the manager know we are done with the cache
+         releaseCacheToManager();
+      }
+      
+      snapshotManager_.stop();
+      
+      // Clean up maps
+      sessions_.clear();
+      unloadedSessions_.clear();
+      
+      passivatedCount_.set(0);
+      
+      started_ = false;
+      
+      // Notify our interested LifecycleListeners
+      lifecycle_.fireLifecycleEvent(AFTER_STOP_EVENT, this);
+      
+      unregisterManagerMBean();
+   }
+   
 
-         try {
-            // Ignore any cache notifications that our own work generates
-            SessionReplicationContext.startCacheActivity();
-            clusterSess.removeMyselfLocal();
-         }
-         finally
-         {
-            SessionReplicationContext.finishCacheActivity();
-            
-            // We don't want to replicate this session at the end
-            // of the request; the removal process took care of that
-            SessionReplicationContext.sessionExpired(clusterSess, realId, snapshotManager_);
-            
-            sessions_.remove(realId);
-            stats_.removeStats(realId);
+   // -------------------------------------------------- JBossCacheManagerMBean
 
-            // Update counters.
-            // It's a bit ad-hoc to do it here. But since we currently call
-            // this when session expires ...
-            expiredCounter_++;
-//            activeCounter_--;
-         }
-      }
+   /**
+    * {@inheritDoc}
+    */
+   public void expireSession(String sessionId)
+   {
+      Session session = findSession(sessionId);
+      if (session != null)
+         session.expire();
    }
 
    /**
-    * Loads a session from the distributed store.  If an existing session with
-    * the id is already under local management, that session's internal state
-    * will be updated from the distributed store.  Otherwise a new session
-    * will be created and added to the collection of those sessions under
-    * local management.
-    *
-    * @param realId  id of the session-id with any jvmRoute removed
-    *
-    * @return the session or <code>null</code> if the session cannot be found
-    *         in the distributed store
+    * {@inheritDoc}
     */
-   protected ClusteredSession loadSession(String realId)
+   public String getCacheConfigName()
    {
-      if (realId == null)
+      return (cacheFromCacheManager_ ? cacheConfigName_ : null);
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public String getCreationTime(String sessionId)
+   {
+      Session session = findSession(sessionId);
+      if(session == null) 
       {
-         return null;
+         log_.info("getCreationTime(): Session " + sessionId + 
+                       " not found");
+         return "";
       }
+     return new Date(session.getCreationTime()).toString();
+   }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public int getDuplicates()
+   {
+      return duplicates_.get();
+   }
 
-      long begin = System.currentTimeMillis();
-      boolean mustAdd = false;
-      JBossCacheClusteredSession session = (JBossCacheClusteredSession) sessions_.get(realId);
-      
-      if (session == null)
-      {                 
-         // This is either the first time we've seen this session on this
-         // server, or we previously expired it and have since gotten
-         // a replication message from another server
-         mustAdd = true;
-         session = createEmptyClusteredSession();
+   /**
+    * {@inheritDoc}
+    */
+   public String getLastAccessedTime(String sessionId)
+   {
+      Session session = findSession(sessionId);
+      if(session == null) {
+         log_.info("getLastAccessedTime(): Session " + sessionId + 
+                   " not found");
+         return "";
       }
+     return new Date(session.getLastAccessedTime()).toString();
+   }
 
-      synchronized (session)
+   /**
+    * {@inheritDoc}
+    */
+   public String getSessionAttribute(String sessionId, String key)
+   {
+      Object attr = null;
+      ClusteredSession session = (ClusteredSession) findSession(sessionId);
+      if (session != null)
       {
-         boolean doTx = false; 
-         try
-         {
-            // We need transaction so any data gravitation replication 
-            // is sent in batch.
-            // Don't do anything if there is already transaction context
-            // associated with this thread.
-            if(tm.getTransaction() == null)
-               doTx = true;
+         attr = session.getAttribute(key);
+      }
+      
+      return attr == null ? null : attr.toString();
+   }
 
-            if(doTx)
-               tm.begin();
-            
-            // Ignore cache notifications we may generate for this 
-            // session if data gravitation occurs. 
-            SessionReplicationContext.startCacheActivity();
-            
-            session = proxy_.loadSession(realId, session);
-            
-            if (session != null)
-            {
-               session.initAfterLoad(this);
-            }
-         }
-         catch (Exception ex)
-         {
-            try
-            {
-//                  if(doTx)
-               // Let's set it no matter what.
-               tm.setRollbackOnly();
-            }
-            catch (Exception exn)
-            {
-               log_.error("Caught exception rolling back transaction", exn);
-            }
-            // We will need to alert Tomcat of this exception.
-            if (ex instanceof RuntimeException)
-               throw (RuntimeException) ex;
-            
-            throw new RuntimeException("loadSession(): failed to load session " +
-                                       realId, ex);
-         }
-         finally
-         {
-            try {
-               if(doTx)
-                  endTransaction(realId);
-            }
-            finally {
-               SessionReplicationContext.finishCacheActivity();
-            }
-         }
+   /**
+    * {@inheritDoc}
+    */
+   public long getMaxPassivatedSessionCount()
+   {
+      return maxPassivatedCount_.get();
+   }
 
-         if (session != null)
-         {            
-            if (mustAdd)
-               add(session, false); // don't replicate
-            long elapsed = System.currentTimeMillis() - begin;
-            stats_.updateLoadStats(realId, elapsed);
+   /**
+    * {@inheritDoc}
+    */
+   public int getMaxUnreplicatedInterval()
+   {
+      return maxUnreplicatedInterval_;
+   }
 
-            if (log_.isTraceEnabled())
-            {
-               log_.trace("loadSession(): id= " + realId + ", session=" + session);
-            }
-         }
-         else if (log_.isTraceEnabled())
-         {
-            log_.trace("loadSession(): session " + realId +
-                       " not found in distributed cache");
-         }
-      }
+   /**
+    * {@inheritDoc}
+    */
+   public void setMaxUnreplicatedInterval(int maxUnreplicatedInterval)
+   {
+      this.maxUnreplicatedInterval_ = maxUnreplicatedInterval;
+   }
 
-      return session;
+   /**
+    * {@inheritDoc}
+    */
+   public long getPassivatedSessionCount()
+   {
+      return passivatedCount_.get();
    }
 
    /**
-    * Places the current session contents in the distributed cache and
-    * replicates them to the cluster
-    *
-    * @param session  the session.  Cannot be <code>null</code>.
+    * {@inheritDoc}
     */
-   protected void processSessionRepl(ClusteredSession session)
+   public long getPassivationMaxIdleTime()
    {
-      // If we are using SESSION granularity, we don't want to initiate a TX
-      // for a single put
-      boolean notSession = (replicationGranularity_ != ReplicationGranularity.SESSION);
-      boolean doTx = false;
-      try
-      {
-         // We need transaction so all the replication are sent in batch.
-         // Don't do anything if there is already transaction context
-         // associated with this thread.
-         if(notSession && tm.getTransaction() == null)
-            doTx = true;
+      return passivationMaxIdleTime_;
+   }
 
-         if(doTx)
-            tm.begin();
+   /**
+    * {@inheritDoc}
+    */
+   public long getPassivationMinIdleTime()
+   {
+      return passivationMinIdleTime_;
+   }
 
-         // Tell the proxy to ignore cache notifications we are about
-         // to generate for this session. We have to do this
-         // at this level because we don't want to resume handling
-         // notifications until any compensating changes resulting
-         // from a tx rollback are done.
-         SessionReplicationContext.startCacheActivity();
+   /**
+    * {@inheritDoc}
+    */
+   public ReplicationGranularity getReplicationGranularity()
+   {
+      return replicationGranularity_;
+   }    
+   
+   /**
+    * {@inheritDoc}
+    */
+   public ReplicationTrigger getReplicationTrigger()
+   {
+      return this.replicationTrigger_;
+   }
 
-         session.processSessionRepl();
+   /**
+    * {@inheritDoc}
+    */
+   public int getSnapshotInterval()
+   {
+      return snapshotInterval_;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public SnapshotMode getSnapshotMode()
+   {
+      return snapshotMode_;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public boolean getUseJK()
+   {
+      return useJK_ == null ? false : useJK_.booleanValue();
+   }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public boolean isPassivationEnabled()
+   {
+      return (passivationMode_ && proxy_.isCachePassivationEnabled());
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public Boolean isReplicationFieldBatchMode()
+   {
+      return replicationFieldBatchMode_;
+   }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public String listLocalSessionIds()
+   {
+      return reportSessionIds(sessions_.keySet());
+   }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public String listSessionIds()
+   {
+      Set ids = new HashSet(sessions_.keySet());
+      ids.addAll(unloadedSessions_.keySet());
+      return reportSessionIds(ids);
+   }
+   
+   // -------------------------------- Callbacks from Distributed Caching Layer
+
+   /**
+    * Notifies the manager that a session in the distributed cache has
+    * been invalidated
+    * 
+    * @param realId the session id excluding any jvmRoute
+    */
+   public void notifyRemoteInvalidation(String realId)
+   {
+      // Remove the session from our local map
+      ClusteredSession session = (ClusteredSession) sessions_.remove(realId);
+      if (session == null)
+      {
+         // We weren't managing the session anyway.  But remove it
+         // from the list of cached sessions we haven't loaded
+         if (unloadedSessions_.remove(realId) != null)
+         {
+            if (trace_)
+               log_.trace("Removed entry for session " + realId + " from unloaded session map");
+         }
       }
-      catch (Exception ex)
+      else
       {
-         log_.debug("processSessionRepl(): failed with exception", ex);
+         // Expire the session
+         // DON'T SYNCHRONIZE ON SESSION HERE -- isValid() and
+         // expire() are meant to be multi-threaded and synchronize
+         // properly internally; synchronizing externally can lead
+         // to deadlocks!!
+         boolean notify = false; // Don't notify listeners. SRV.10.7
+                                 // allows this, and sending notifications
+                                 // leads to all sorts of issues; e.g.
+                                 // circular calls with ClusteredSSO
+         boolean localCall = false; // this call originated from the cache;
+                                    // we have already removed session
+         boolean localOnly = true; // Don't pass attr removals to cache
          
+         // Ensure the correct TCL is in place
+         ClassLoader prevTcl = Thread.currentThread().getContextClassLoader();
          try
          {
-            //if(doTx)
-            // Let's setRollbackOnly no matter what.
-            // (except if there's no tx due to SESSION (JBAS-3840))
-            if (notSession)
-               tm.setRollbackOnly();
+            Thread.currentThread().setContextClassLoader(tcl_);
+            session.expire(notify, localCall, localOnly);
          }
-         catch (Exception exn)
+         finally
          {
-            log_.error("Caught exception rolling back transaction", exn);
+            Thread.currentThread().setContextClassLoader(prevTcl);
          }
-         
-         // We will need to alert Tomcat of this exception.
-         if (ex instanceof RuntimeException)
-            throw (RuntimeException) ex;
-         
-         throw new RuntimeException("JBossCacheManager.processSessionRepl(): " +
-                                    "failed to replicate session.", ex);
+
+         // Remove any stats for this session
+         stats_.removeStats(realId);
       }
-      finally
+   }
+   
+   /**
+    * Callback from the distributed cache notifying of a local modification
+    * to a session's attributes.  Meant for use with FIELD granularity,
+    * where the session may not be aware of modifications.
+    * 
+    * @param realId the session id excluding any jvmRoute
+    */
+   public void notifyLocalAttributeModification(String realId)
+   {
+      ClusteredSession session = (ClusteredSession) sessions_.get(realId);
+      if (session != null)
       {
-         try {
-            if(doTx)
-               endTransaction(session.getId());
-         }
-         finally {
-            SessionReplicationContext.finishCacheActivity();
-         }
+         session.sessionAttributesDirty();
       }
+      else
+      {
+         log_.warn("");
+      }
    }
-
-   protected void endTransaction(String id)
+   
+   public void sessionActivated()
    {
-      if (tm == null)
+      int pc = passivatedCount_.decrementAndGet();
+      // Correct for drift since we don't know the true passivation
+      // count when we started.  We can get activations of sessions
+      // we didn't know were passivated.
+      // FIXME -- is the above statement still correct? Is this needed?
+      if (pc < 0) 
       {
-         log_.warn("JBossCacheManager.endTransaction(): tm is null for id: " +id);
-         return;
+         // Just reverse our decrement.
+         passivatedCount_.incrementAndGet();
       }
-
-      try
+   }
+   
+   /**
+    * Callback from the distributed cache to notify us that a session
+    * has been modified remotely.
+    * 
+    * @param realId the session id, without any trailing jvmRoute
+    * @param dataOwner  the owner of the session.  Can be <code>null</code> if
+    *                   the owner is unknown.
+    * @param distributedVersion the session's version per the distributed cache
+    * @param timestamp the session's timestamp per the distributed cache
+    * @param metadata the session's metadata per the distributed cache
+    */
+   public boolean sessionChangedInDistributedCache(String realId, 
+                                         String dataOwner,
+                                         int distributedVersion,
+                                         SessionTimestamp timestamp, 
+                                         SessionMetadata metadata)
+   {
+      boolean updated = true;
+      
+      ClusteredSession session = findLocalSession(realId);
+      if (session != null)
       {
-         if(tm.getTransaction().getStatus() != Status.STATUS_MARKED_ROLLBACK)
+         // Need to invalidate the loaded session. We get back whether
+         // this an actual version increment
+         updated = session.setVersionFromDistributedCache(distributedVersion);
+         if (updated && trace_)      
+         {            
+            log_.trace("session in-memory data is invalidated for id: " + realId + 
+                       " new version: " + distributedVersion);
+         }         
+      }
+      else
+      {
+         long lastMod = timestamp == null ? System.currentTimeMillis() : timestamp.timestamp;
+         int maxLife = metadata == null ? getMaxInactiveInterval() : metadata.maxInactiveInterval;
+         
+         Object existing = unloadedSessions_.put(realId, new OwnedSessionUpdate(dataOwner, lastMod, maxLife, false));
+         if (existing == null)
          {
-            tm.commit();
+            calcActiveSessions();
+            if (trace_)
+            {
+               log_.trace("New session " + realId + " added to unloaded session map");
+            }
          }
-         else
+         else if (trace_)
          {
-            log_.info("JBossCacheManager.endTransaction(): rolling back tx for id: " +id);
-            tm.rollback();
+            log_.trace("Updated timestamp for unloaded session " + realId);
          }
       }
-      catch (RollbackException re)
-      {
-         // Do nothing here since cache may rollback automatically.
-         log_.warn("JBossCacheManager.endTransaction(): rolling back transaction with exception: " +re);
-      }
-      catch (Exception e)
-      {
-         throw new RuntimeException("JBossCacheManager.endTransaction(): Exception for id: " +id, e);
-      }
+      
+      return updated;
    }
    
+   // ----------------------------------------------- JBossCacheCluster Support
+
    /**
-    * Gets the classloader of the webapp we are managing.
+    * Sets how often session changes should be replicated to other nodes.
+    *
+    * @param snapshotInterval the number of milliseconds between
+    *                         session replications.
     */
-   protected ClassLoader getWebappClassLoader()
+   public void setSnapshotInterval(int snapshotInterval)
    {
-      return tcl_;
+      this.snapshotInterval_ = snapshotInterval;
    }
 
    /**
-    * Goes through all sessions and look if they have expired.
-    * Note this overrides the method in JBossManager.
+    * Sets when sessions are replicated to the other nodes. Valid values are:
+    * <ul>
+    * <li>instant</li>
+    * <li>interval</li>
+    * </ul>
     */
+   public void setSnapshotMode(SnapshotMode snapshotMode)
+   {
+      this.snapshotMode_ = snapshotMode;
+   }
+
+   /**
+    * Sets whether the <code>Engine</code> in which we are running
+    * uses <code>mod_jk</code>.
+    */
+   public void setUseJK(boolean useJK)
+   {
+      this.useJK_ = Boolean.valueOf(useJK);
+   }
+
+   /**
+    * Sets the granularity of session data replicated across the cluster.
+    * Valid values are:
+    * <ul>
+    * <li>SESSION</li>
+    * <li>ATTRIBUTE</li>
+    * <li>FIELD</li>
+    * </ul>
+    */
+   public void setReplicationGranularity(ReplicationGranularity granularity)
+   {
+      this.replicationGranularity_ = granularity;
+   }
+
+   /**
+    * Returns the replication granularity.
+    *
+    *  @see JBossWebMetaData#REPLICATION_GRANULARITY_ATTRIBUTE
+    *  @see JBossWebMetaData#REPLICATION_GRANULARITY_FIELD
+    *  @see JBossWebMetaData#REPLICATION_GRANULARITY_SESSION
+    */
+   public String getReplicationGranularityString()
+   {
+      return replicationGranularity_ == null ? null : replicationGranularity_.toString();
+   }
+
+   /**
+    * Sets the granularity of session data replicated across the cluster.
+    * Valid values are:
+    * <ul>
+    * <li>SESSION</li>
+    * <li>ATTRIBUTE</li>
+    * <li>FIELD</li>
+    * </ul>
+    */
+   public void setReplicationGranularityString(String granularity)
+   {
+      setReplicationGranularity(granularity == null ? null : 
+                  ReplicationGranularity.fromString(granularity.toUpperCase()));
+   } 
+   
+   /**
+    * Sets the type of operations on a <code>HttpSession</code> that
+    * trigger replication.  Valid values are:
+    * <ul>
+    * <li>SET_AND_GET</li>
+    * <li>SET_AND_NON_PRIMITIVE_GET</li>
+    * <li>SET</li>
+    * </ul>
+    */
+   public void setReplicationTrigger(ReplicationTrigger trigger)
+   {
+      this.replicationTrigger_ = trigger;
+   }
+
+   public String getReplicationTriggerString()
+   {      
+      return replicationTrigger_ == null ? null : replicationTrigger_.toString();
+   }
+
+   public void setReplicationTriggerString(String trigger)
+   {      
+      setReplicationTrigger(trigger == null ? null : ReplicationTrigger.fromString(trigger.toUpperCase()));
+   }
+
+   /**
+    * Sets whether, if replication granularity is set to <code>FIELD</code>,
+    * replication should be done in batch mode.  Ignored if field-level
+    * granularity is not used.
+    */
+   public void setReplicationFieldBatchMode(boolean replicationFieldBatchMode)
+   {
+      this.replicationFieldBatchMode_ = Boolean.valueOf(replicationFieldBatchMode);
+   }
+
+   // -------------------------------------------------------------  Properties
+   
+   public PojoCache getPojoCache()
+   {
+      return pojoCache_;
+   }
+   
+   public Cache getPlainCache()
+   {
+      return plainCache_;
+   }
+   
+   /**
+    * Gets the <code>FieldBasedJBossCacheService</code> through which we interact
+    * with the PojoCache.
+    * 
+    * @throws IllegalStateException if we are not using field based replication
+    */
+   public FieldBasedJBossCacheService getPojoCacheService()
+   {
+      if (proxy_ != null && !(proxy_ instanceof FieldBasedJBossCacheService))
+         throw new IllegalStateException("PojoCache not being used");
+      return (FieldBasedJBossCacheService) proxy_;
+   }
+
+   /**
+    * Gets the <code>JBossCacheService</code> through which we interact
+    * with the <code>Cache</code>.
+    */
+   public JBossCacheService getCacheService()
+   {
+      return proxy_;
+   }
+
+   // --------------------------------------------------------------- Overrides
+
+   /**
+    * {@inheritDoc}
+    * <p>
+    * Overrides the superclass version to ensure that the generated id
+    * does not duplicate the id of any other session this manager is aware of.
+    * </p>
+    */
    @Override
-   protected void processExpires()
+   protected String getNextId()
+   {
+      while (true)
+      {
+         String id = super.getNextId();
+         if (sessions_.containsKey(id) || unloadedSessions_.containsKey(id))
+         {
+            duplicates_.incrementAndGet();
+         }
+         else
+         {
+            return id;
+         }
+      }
+   }
+
+   protected int getTotalActiveSessions()
+   {
+      return localActiveCounter_.get() + unloadedSessions_.size() - passivatedCount_.get();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected void processExpirationPassivation()
    {      
       boolean expire = maxInactiveInterval_ >= 0;
       boolean passivate = isPassivationEnabled();
@@ -1395,15 +1220,14 @@
       long passivationMax = passivationMaxIdleTime_ * 1000L;
       long passivationMin = passivationMinIdleTime_ * 1000L;
 
-      boolean trace = log_.isTraceEnabled();
-      if (trace)
+      if (trace_)
       { 
-         log_.trace("processExpires(): Looking for sessions that have expired ...");
-         log_.trace("processExpires(): active sessions = " + calcActiveSessions());
-         log_.trace("processExpires(): expired sessions = " + expiredCounter_);
+         log_.trace("processExpirationPassivation(): Looking for sessions that have expired ...");
+         log_.trace("processExpirationPassivation(): active sessions = " + calcActiveSessions());
+         log_.trace("processExpirationPassivation(): expired sessions = " + expiredCounter_);
          if (passivate)
          {
-            log_.trace("processExpires(): passivated count = " + getPassivatedSessionCount());
+            log_.trace("processExpirationPassivation(): passivated count = " + getPassivatedSessionCount());
          }
       }
       
@@ -1418,7 +1242,7 @@
                ClusteredSession session = (ClusteredSession) sessions[i];
                if(session == null)
                {
-                  log_.warn("processExpires(): processing null session at index " +i);
+                  log_.warn("processExpirationPassivation(): processing null session at index " +i);
                   continue;
                }
 
@@ -1443,7 +1267,6 @@
                   // to deadlocks!!
                   if (!session.isValid()) continue;
                }
-               // JBCLUSTER-15
                 
                // we now have a valid session; see if we need to passivate it
                if (passivate)
@@ -1460,9 +1283,9 @@
                   // If the session didn't exceed the passivationMaxIdleTime_, see   
                   // if the number of sessions managed by this manager greater than the max allowed 
                   // active sessions, passivate the session if it exceed passivationMinIdleTime_ 
-                  else if (maxActive_ > 0 
+                  else if (maxActiveAllowed_ > 0 
                               && passivationMin > 0 
-                              && calcActiveSessions() >= maxActive_ 
+                              && calcActiveSessions() >= maxActiveAllowed_ 
                               && timeIdle > passivationMin)
                   {
                      processSessionPassivation(session.getRealId());
@@ -1472,31 +1295,34 @@
             }
             catch (Exception ex)
             {
-               log_.error("processExpires(): failed expiring " + 
+               log_.error("processExpirationPassivation(): failed handling " + 
                           sessions[i].getIdInternal() + " with exception: " + 
                           ex, ex);
             }
          }
 
-         // Next, handle any unloaded sessions that are stale
+         // Next, handle any unloaded sessions
 
-         long now = System.currentTimeMillis();
-         Map unloaded = new HashMap(unloadedSessions_);
-         Set entries = unloaded.entrySet();
+         
          // We may have not gotten replication of a timestamp for requests 
          // that occurred w/in maxUnreplicatedInterval_ of the previous
          // request. So we add a grace period to avoid flushing a session early
          // and permanently losing part of its node structure in JBoss Cache.
          long maxUnrep = maxUnreplicatedInterval_ < 0 ? 60 : maxUnreplicatedInterval_;
-         for (Iterator it = entries.iterator(); it.hasNext(); )
+         
+         Map unloaded = new HashMap(unloadedSessions_);
+         for (Iterator<Map.Entry<String, OwnedSessionUpdate>> it = unloaded.entrySet().iterator(); it.hasNext(); )
          {
-            Map.Entry entry = (Map.Entry) it.next();
-            String realId = (String) entry.getKey();
-            OwnedSessionUpdate osu = (OwnedSessionUpdate) entry.getValue();
+            Map.Entry<String, OwnedSessionUpdate> entry = it.next();
+            String realId = entry.getKey();
+            OwnedSessionUpdate osu = entry.getValue();
+            
             // Ignore marker entries for our own passivated sessions
             // Also skip if the session isn't configured to expire
             if (osu.passivated || osu.maxInactive < 1)
                continue;
+            
+            long now = System.currentTimeMillis();
             long elapsed = (now - osu.updateTime);
             try
             {
@@ -1514,163 +1340,263 @@
                   {
                      processUnloadedSessionPassivation(realId, osu);
                   }
-                  // If the session didn't exceed the passivationMaxIdleTime_, See   
-                  // if the number of sessions managed by this manager greater than the max allowed 
-                  // active sessions, passivate the session if it exceed passivationMinIdleTime_ 
-                  else if (maxActive_ > 0 
+                  // If the session didn't exceed the passivationMaxIdleTime_, see   
+                  // if the number of sessions managed by this manager is greater than the max allowed 
+                  // active sessions; passivate the session if it exceed passivationMinIdleTime_ 
+                  else if (maxActiveAllowed_ > 0 
                               && passivationMin >= 0 
-                              && calcActiveSessions() >= maxActive_ 
-                              && elapsed >= passivationMin)
+                              && elapsed >= passivationMin
+                              && calcActiveSessions() >= maxActiveAllowed_)
                   {
                      processUnloadedSessionPassivation(realId, osu);
                   }               
                }
-            }               
-            // JBClUSTER-15
-            // we don't need to worry about session passivation here, since the 
-            // method processSessionPassivation() takes care of unloadedSessions_ map
-            // when it receives a notification of passivation event happened in the 
-            // distributed store from the CacheListener
+            } 
             catch (Exception ex)
             {
-               log_.error("processExpires(): failed removing unloaded session " + 
+               log_.error("processExpirationPassivation(): failed handling unloaded session " + 
                        realId, ex);
             }
          }
       }
       catch (Exception ex)
       {
-         log_.error("processExpires: failed with exception: " + ex, ex);
+         log_.error("processExpirationPassivation(): failed with exception: " + ex, ex);
       }
       
-      if (trace)
+      if (trace_)
       { 
-         log_.trace("processExpires(): Completed ...");
-         log_.trace("processExpires(): active sessions = " + calcActiveSessions());
-         log_.trace("processExpires(): expired sessions = " + expiredCounter_);
+         log_.trace("processExpirationPassivation(): Completed ...");
+         log_.trace("processExpirationPassivation(): active sessions = " + calcActiveSessions());
+         log_.trace("processExpirationPassivation(): expired sessions = " + expiredCounter_);
          if (passivate)
          {
-            log_.trace("processExpires(): passivated count = " + getPassivatedSessionCount());
+            log_.trace("processExpirationPassivation(): passivated count = " + getPassivatedSessionCount());
          }
       }
    }
+   
+   @Override
+   public void resetStats()
+   {
+      super.resetStats();
+      
+      this.maxPassivatedCount_.set(this.passivatedCount_.get());
+   }
+   
+   
+   
+   // --------------------------------------------------------------- Protected
 
    /**
-    * Notifies the manager that a session in the distributed cache has
-    * been invalidated
+    * Gets the classloader of the webapp we are managing.
     * 
-    * @param realId the session id excluding any jvmRoute
+    * FIXME try and remove the need for this
     */
-   public void notifyRemoteInvalidation(String realId)
+   protected ClassLoader getWebappClassLoader()
    {
-      // Remove the session from our local map
-      ClusteredSession session = (ClusteredSession) sessions_.remove(realId);
-      if (session == null)
+      return tcl_;
+   }
+   
+   // ------------------------------------------------------ Session Management
+
+   private JBossCacheClusteredSession createEmptyClusteredSession()
+   {     
+
+      JBossCacheClusteredSession session = null;
+      switch (replicationGranularity_)
       {
-         // We weren't managing the session anyway.  But remove it
-         // from the list of cached sessions we haven't loaded
-         if (unloadedSessions_.remove(realId) != null)
-         {
-            if (log_.isTraceEnabled())
-               log_.trace("Removed entry for session " + realId + " from unloaded session map");
-         }
+         case ATTRIBUTE:
+         session = new AttributeBasedClusteredSession(this);
+            break;
+         case FIELD:
+            session = new FieldBasedClusteredSession(this);
+            break;
+         default:
+            session = new SessionBasedClusteredSession(this);
+            break;
       }
-      else
+      return session;
+   }
+   
+   /**
+    * Loads a session from the distributed store.  If an existing session with
+    * the id is already under local management, that session's internal state
+    * will be updated from the distributed store.  Otherwise a new session
+    * will be created and added to the collection of those sessions under
+    * local management.
+    *
+    * @param realId  id of the session-id with any jvmRoute removed
+    *
+    * @return the session or <code>null</code> if the session cannot be found
+    *         in the distributed store
+    */
+   private ClusteredSession loadSession(String realId)
+   {
+      if (realId == null)
       {
-         // Expire the session
-         // DON'T SYNCHRONIZE ON SESSION HERE -- isValid() and
-         // expire() are meant to be multi-threaded and synchronize
-         // properly internally; synchronizing externally can lead
-         // to deadlocks!!
-         boolean notify = false; // Don't notify listeners. SRV.10.7
-                                 // allows this, and sending notifications
-                                 // leads to all sorts of issues; e.g.
-                                 // circular calls with ClusteredSSO
-         boolean localCall = false; // this call originated from the cache;
-                                    // we have already removed session
-         boolean localOnly = true; // Don't pass attr removals to cache
-         
-         // Ensure the correct TCL is in place
-         ClassLoader prevTcl = Thread.currentThread().getContextClassLoader();
+         return null;
+      }
+
+      long begin = System.currentTimeMillis();
+      boolean mustAdd = false;
+      JBossCacheClusteredSession session = (JBossCacheClusteredSession) sessions_.get(realId);
+      
+      if (session == null)
+      {                 
+         // This is either the first time we've seen this session on this
+         // server, or we previously expired it and have since gotten
+         // a replication message from another server
+         mustAdd = true;
+         session = createEmptyClusteredSession();
+      }
+
+      synchronized (session)
+      {
+         boolean doTx = false; 
          try
          {
-            Thread.currentThread().setContextClassLoader(tcl_);
-            session.expire(notify, localCall, localOnly);
+            // We need transaction so any data gravitation replication 
+            // is sent in batch.
+            // Don't do anything if there is already transaction context
+            // associated with this thread.
+            if(tm.getTransaction() == null)
+               doTx = true;
+
+            if(doTx)
+               tm.begin();
+            
+            // Ignore cache notifications we may generate for this 
+            // session if data gravitation occurs. 
+            SessionReplicationContext.startCacheActivity();
+            
+            session = proxy_.loadSession(realId, session);
+            
+            if (session != null)
+            {
+               session.initAfterLoad(this);
+            }
          }
+         catch (Exception ex)
+         {
+            try
+            {
+//                  if(doTx)
+               // Let's set it no matter what.
+               tm.setRollbackOnly();
+            }
+            catch (Exception exn)
+            {
+               log_.error("Caught exception rolling back transaction", exn);
+            }
+            // We will need to alert Tomcat of this exception.
+            if (ex instanceof RuntimeException)
+               throw (RuntimeException) ex;
+            
+            throw new RuntimeException("loadSession(): failed to load session " +
+                                       realId, ex);
+         }
          finally
          {
-            Thread.currentThread().setContextClassLoader(prevTcl);
+            try {
+               if(doTx)
+                  endTransaction(realId);
+            }
+            finally {
+               SessionReplicationContext.finishCacheActivity();
+            }
          }
 
-         // Remove any stats for this session
-         stats_.removeStats(realId);
+         if (session != null)
+         {            
+            if (mustAdd)
+               add(session, false); // don't replicate
+            long elapsed = System.currentTimeMillis() - begin;
+            stats_.updateLoadStats(realId, elapsed);
+
+            if (trace_)
+            {
+               log_.trace("loadSession(): id= " + realId + ", session=" + session);
+            }
+         }
+         else if (trace_)
+         {
+            log_.trace("loadSession(): session " + realId +
+                       " not found in distributed cache");
+         }
       }
+
+      return session;
    }
-   
+
    /**
-    * Callback from the distributed cache notifying of a local modification
-    * to a session's attributes.  Meant for use with FIELD granularity,
-    * where the session may not be aware of modifications.
-    * 
-    * @param realId the session id excluding any jvmRoute
+    * Places the current session contents in the distributed cache and
+    * replicates them to the cluster
+    *
+    * @param session  the session.  Cannot be <code>null</code>.
     */
-   public void notifyLocalAttributeModification(String realId)
+   private void processSessionRepl(ClusteredSession session)
    {
-      ClusteredSession session = (ClusteredSession) sessions_.get(realId);
-      if (session != null)
+      // If we are using SESSION granularity, we don't want to initiate a TX
+      // for a single put
+      boolean notSession = (replicationGranularity_ != ReplicationGranularity.SESSION);
+      boolean doTx = false;
+      try
       {
-         session.sessionAttributesDirty();
+         // We need transaction so all the replication are sent in batch.
+         // Don't do anything if there is already transaction context
+         // associated with this thread.
+         if(notSession && tm.getTransaction() == null)
+            doTx = true;
+
+         if(doTx)
+            tm.begin();
+
+         // Tell the proxy to ignore cache notifications we are about
+         // to generate for this session. We have to do this
+         // at this level because we don't want to resume handling
+         // notifications until any compensating changes resulting
+         // from a tx rollback are done.
+         SessionReplicationContext.startCacheActivity();
+
+         session.processSessionRepl();
       }
-      else
+      catch (Exception ex)
       {
-         log_.warn("");
+         log_.debug("processSessionRepl(): failed with exception", ex);
+         
+         try
+         {
+            //if(doTx)
+            // Let's setRollbackOnly no matter what.
+            // (except if there's no tx due to SESSION (JBAS-3840))
+            if (notSession)
+               tm.setRollbackOnly();
+         }
+         catch (Exception exn)
+         {
+            log_.error("Caught exception rolling back transaction", exn);
+         }
+         
+         // We will need to alert Tomcat of this exception.
+         if (ex instanceof RuntimeException)
+            throw (RuntimeException) ex;
+         
+         throw new RuntimeException("JBossCacheManager.processSessionRepl(): " +
+                                    "failed to replicate session.", ex);
       }
-   }
-   
-   private void sessionPassivated()
-   {
-      int pc = passivatedCount_.incrementAndGet();
-      if (pc > maxPassivatedCount_)
-         maxPassivatedCount_ = pc;
-   }
-   
-   public void sessionActivated()
-   {
-      int pc = passivatedCount_.decrementAndGet();
-      // Correct for drift since we don't know the true passivation
-      // count when we started.  We can get activations of sessions
-      // we didn't know were passivated.
-      // FIXME -- is the above statement still correct? Is this needed?
-      if (pc < 0) 
+      finally
       {
-         // Just reverse our decrement.
-         passivatedCount_.incrementAndGet();
+         try {
+            if(doTx)
+               endTransaction(session.getId());
+         }
+         finally {
+            SessionReplicationContext.finishCacheActivity();
+         }
       }
    }
    
-   /** 
-    * Calculates the number of active sessions, and updates
-    * the max # of local active sessions and max # of sessions.
-    * <p>
-    * Call this method when a new session is added or when an
-    * accurate count of active sessions is needed.
-    * </p>
-    * 
-    * @return the size of the sessions map + the size of the unloaded sessions 
-    *         map - the count of passivated sessions
-    */
-   private int calcActiveSessions()
-   {
-      activeCounter_ = sessions_.size();
-      if (activeCounter_ > maxLocalActiveCounter_)
-         maxLocalActiveCounter_ = activeCounter_;
-      
-      int count = activeCounter_ + unloadedSessions_.size() - passivatedCount_.get();
-      if (count > maxActiveCounter_)
-         maxActiveCounter_ = count;
-      return count;
-   }
-   
    /**
     * Session passivation logic for an actively managed session.
     * 
@@ -1686,7 +1612,7 @@
       {
          synchronized (session)
          {
-            if (log_.isTraceEnabled())
+            if (trace_)
             {
                log_.trace("Passivating session with id: " + realId);
             }
@@ -1707,7 +1633,7 @@
             // expose the session to regular invalidation.
             Object obj = unloadedSessions_.put(realId, 
                   new OwnedSessionUpdate(null, session.getLastAccessedTime(), session.getMaxInactiveInterval(), true));
-            if (log_.isTraceEnabled())
+            if (trace_)
             {
                if (obj == null)
                {
@@ -1719,11 +1645,9 @@
                }
             }
             sessions_.remove(realId);
-//            stats_.removeStats(realId);
          }
-//         activeCounter_--;
       }
-      else if (log_.isTraceEnabled())
+      else if (trace_)
       {
          log_.trace("processSessionPassivation():  could not find session " + realId);
       }
@@ -1736,7 +1660,7 @@
     */
    private void processUnloadedSessionPassivation(String realId, OwnedSessionUpdate osu)
    {
-      if (log_.isTraceEnabled())
+      if (trace_)
       {
          log_.trace("Passivating session with id: " + realId);
       }
@@ -1754,112 +1678,20 @@
       }
       
    }
-
-   /**
-    * Gets the session id with any jvmRoute removed.
-    * 
-    * @param id a session id with or without an appended jvmRoute.
-    *           Cannot be <code>null</code>.
-    */
-   protected String getRealId(String id)
-   {
-      return (getUseJK() ? Util.getRealId(id) : id);
-   }
    
-   /**
-    * Callback from the CacheListener to notify us that a session
-    * we haven't loaded has been changed.
-    * 
-    * @param realId the session id, without any trailing jvmRoute
-    * @param dataOwner  the owner of the session.  Can be <code>null</code> if
-    *                   the owner is unknown.
-    */
-   protected void unloadedSessionChanged(String realId, 
-                                         String dataOwner,
-                                         SessionTimestamp timestamp,
-                                         SessionMetadata metadata)
+   private void sessionPassivated()
    {
-      long lastMod = timestamp == null ? System.currentTimeMillis() : timestamp.timestamp;
-      int maxLife = metadata == null ? getMaxInactiveInterval() : metadata.maxInactiveInterval;
-      Object obj = unloadedSessions_.put(realId, 
-            new OwnedSessionUpdate(dataOwner, lastMod, maxLife, false));
-      if (log_.isTraceEnabled())
+      int pc = passivatedCount_.incrementAndGet();
+      int max = maxPassivatedCount_.get();
+      while (pc > max)
       {
-         if (obj == null)
+         if (!maxPassivatedCount_ .compareAndSet(max, pc))
          {
-            log_.trace("New session " + realId + " added to unloaded session map");
-            calcActiveSessions();
+            max = maxPassivatedCount_.get();
          }
-         else
-         {
-            log_.trace("Updated timestamp for unloaded session " + realId);
-         }
       }
    }
    
-   /**
-    * Returns true if the passivation mode is set to true in jboss-web.xml and JBoss Cache passivation
-    * has been enabled with proper configured cache loader. Otherwise, it returns false
-    * 
-    * @return
-    */
-   public boolean isPassivationEnabled()
-   {
-      return (passivationMode_ && proxy_.isCachePassivationEnabled());
-   }
-   
-   // ------------------------------------------------------  Lifecyle Embedded
-   
-   /**
-    * Start this Manager when running embedded in JBoss AS.
-    *
-    * @throws org.apache.catalina.LifecycleException
-    */
-   private void startEmbedded() throws LifecycleException
-   {
-      super.start();
-      
-      // Start the JBossCacheService
-      // Will need to pass the classloader that is associated with this 
-      // web app so de-serialization will work correctly.
-      tcl_ = super.getContainer().getLoader().getClassLoader();
-      
-      try
-      {
-         if (proxy_ == null) // Could happen during a restart
-         {
-            initCacheProxy();
-         }
-         
-         proxy_.start(tcl_, this);
-
-         tm = proxy_.getTransactionManager();
-         if(tm == null)
-         {
-            throw new LifecycleException("JBossCacheManager.start(): Obtain null tm");
-         }
-         
-         initializeUnloadedSessions();
-         
-         // Setup our SnapshotManager
-         initSnapshotManager();
-         
-         // Add SnapshotValve and, if needed, JvmRouteValve and batch repl valve
-         installValves();
-
-         log_.debug("start(): JBossCacheService started");         
-      }
-      catch (LifecycleException le)
-      {
-         throw le;
-      }
-      catch (Exception e)
-      {
-         log_.error("Unable to start manager.", e);
-         throw new LifecycleException(e);
-      }
-   }
-   
    // -----------------------------------------------  Lifecyle When Unembedded
 
    /**
@@ -1943,88 +1775,58 @@
          throw new LifecycleException(e);
       }
       
-      try
-      {
-         registerMBeans();
-      }
-      catch (Exception e)
-      {
-         log_.error("Could not register ManagerMBean with MBeanServer", e);
-      }
+      registerManagerMBean();
    }
 
    /**
-    * Register this Manager with JMX.
+    * Accesses the underlying cache and creates the proxy
+    * 
+    * @throws ClusteringNotSupportedException
     */
-   private void registerMBeans() 
+   private void initCacheProxy() throws ClusteringNotSupportedException
    {
+      // We are likely going to cause creation and start of a cache here;
+      // we don't want to leak the TCCL to cache/jgroups threads,
+      // so we switch it to our classloader
+      ContextClassLoaderSwitcher switcher = (ContextClassLoaderSwitcher) AccessController.doPrivileged(ContextClassLoaderSwitcher.INSTANTIATOR);
+      ContextClassLoaderSwitcher.SwitchContext switchContext = switcher.getSwitchContext(getClass().getClassLoader());
       try
       {
-         MBeanServer server = getMBeanServer();
-
-         String domain;
-         if (container_ instanceof ContainerBase)
+         if (ReplicationGranularity.FIELD == replicationGranularity_)
          {
-            domain = ((ContainerBase) container_).getDomain();
+            PojoCache pojoC = getPojoCache();
+            if (pojoC == null)
+            {
+               pojoC = Util.findPojoCache(cacheConfigName_);
+               
+               cacheFromCacheManager_ = true;
+               
+               if (pojoC.getCache().getCacheStatus() != CacheStatus.STARTED)
+                  pojoC.getCache().start();
+            }
+            proxy_ = new FieldBasedJBossCacheService(pojoC);
          }
          else
          {
-            domain = server.getDefaultDomain();
+            Cache pc = getPlainCache();
+            if (pc == null)
+            {
+               pc = Util.findPlainCache(cacheConfigName_);
+               
+               cacheFromCacheManager_ = true;
+               
+               if (pc.getCacheStatus() != CacheStatus.STARTED)
+                  pc.start();
+            }
+            proxy_ = new JBossCacheService(pc);
          }
-         String hostName = ((Host) container_.getParent()).getName();
-         hostName = (hostName == null) ? "localhost" : hostName;
-         ObjectName clusterName = new ObjectName(domain
-               + ":service=ClusterManager,WebModule=//" + hostName
-               + ((Context) container_).getPath());
-
-         if (server.isRegistered(clusterName))
-         {
-            log_.warn("MBean " + clusterName + " already registered");
-            return;
-         }
-
-         objectName_ = clusterName;
-         server.registerMBean(this, clusterName);
-
       }
-      catch (Exception ex)
+      finally
       {
-         log_.error(ex.getMessage(), ex);
+         // Restore the TCCL
+         switchContext.reset();
       }
    }
-
-   /**
-    * Unregister this Manager from the JMX server.
-    */
-   private void unregisterMBeans()
-   {
-      if (mserver_ != null)
-      {
-         try
-         {
-            mserver_.unregisterMBean(objectName_);
-         }
-         catch (Exception e)
-         {
-            log_.error(e);
-         }
-      }
-   }
-
-   /**
-    * Get the current MBean Server.
-    * 
-    * @return
-    * @throws Exception
-    */
-   private MBeanServer getMBeanServer() throws Exception
-   {
-      if (mserver_ == null)
-      {
-         mserver_ = MBeanServerLocator.locateJBoss();
-      }
-      return (mserver_);
-   }
    
    /**
     * Gets the ids of all sessions in the distributed cache and adds
@@ -2076,20 +1878,26 @@
                   if (passivationMax >= 0 
                         && elapsed > passivationMax)
                   {
-                     if (log_.isTraceEnabled())
-                        log_.trace("Elapsed time of " + elapsed + " for session "+ realId + " exceeds max of " + passivationMax + "; passivating");
+                     if (trace_)
+                     {
+                        log_.trace("Elapsed time of " + elapsed + " for session "+ 
+                              realId + " exceeds max of " + passivationMax + "; passivating");
+                     }
                      processUnloadedSessionPassivation(realId, osu);
                   }
                   // If the session didn't exceed the passivationMaxIdleTime_, see   
                   // if the number of sessions managed by this manager greater than the max allowed 
                   // active sessions, passivate the session if it exceed passivationMinIdleTime_ 
-                  else if (maxActive_ > 0 
+                  else if (maxActiveAllowed_ > 0 
                               && passivationMin >= 0 
-                              && calcActiveSessions() > maxActive_ 
+                              && calcActiveSessions() > maxActiveAllowed_ 
                               && elapsed >= passivationMin)
                   {
-                     if (log_.isTraceEnabled())
-                        log_.trace("Elapsed time of " + elapsed + " for session "+ realId + " exceeds min of " + passivationMin + "; passivating");
+                     if (trace_)
+                     {
+                        log_.trace("Elapsed time of " + elapsed + " for session "+ 
+                              realId + " exceeds min of " + passivationMin + "; passivating");
+                     }
                      processUnloadedSessionPassivation(realId, osu);
                   }
                }
@@ -2159,7 +1967,8 @@
       }
       else if (ReplicationGranularity.FIELD == replicationGranularity_)
       {
-         throw new IllegalStateException("Property snapshotMode must be " + SnapshotMode.INTERVAL + " when FIELD granularity is used");
+         throw new IllegalStateException("Property snapshotMode must be " + 
+               SnapshotMode.INTERVAL + " when FIELD granularity is used");
       }
       else if (snapshotInterval_ < 1)
       {
@@ -2253,7 +2062,7 @@
       }
    }
    
-   public void releaseCacheToManager()
+   private void releaseCacheToManager()
    {      
       try
       {
@@ -2273,22 +2082,202 @@
          log_.error("Problem releasing cache to CacheManager -- config is " + cacheConfigName_, e);
       }
    }
+
+   /**
+    * Clear the underlying cache store.
+    */
+   private void clearSessions()
+   {
+      boolean passivation = isPassivationEnabled();
+      // First, the sessions we have actively loaded
+      ClusteredSession[] sessions = findLocalSessions();
+      for(int i=0; i < sessions.length; i++)
+      {
+         ClusteredSession ses = sessions[i];
+         
+         if (trace_)
+         {
+             log_.trace("clearSessions(): clear session by expiring or passivating: " + ses);
+         }
+         try
+         {
+            // if session passivation is enabled, passivate sessions instead of expiring them which means
+            // they'll be available to the manager for activation after a restart. 
+            if(passivation && ses.isValid())
+            {               
+               processSessionPassivation(ses.getRealId());
+            }
+            else
+            {               
+               boolean notify = true;
+               boolean localCall = true;
+               boolean localOnly = true;
+               ses.expire(notify, localCall, localOnly);               
+            }
+         }
+         catch (Throwable t)
+         {
+            log_.warn("clearSessions(): Caught exception expiring or passivating session " +
+                     ses.getIdInternal(), t);
+         }
+         finally
+         {
+            // Guard against leaking memory if anything is holding a
+            // ref to the session by clearing its internal state
+            ses.recycle();
+         }
+      }      
+      
+      String action = passivation ? "evicting" : "removing";
+      Set<Map.Entry<String, OwnedSessionUpdate>> unloaded = 
+               unloadedSessions_.entrySet();
+      for (Iterator<Map.Entry<String, OwnedSessionUpdate>> it = unloaded.iterator(); it.hasNext();)
+      {
+         Map.Entry<String, OwnedSessionUpdate> entry = it.next();
+         String realId = entry.getKey();         
+         try
+         {
+            if (passivation)
+            {
+               OwnedSessionUpdate osu = entry.getValue();
+               // Ignore the marker entries for our passivated sessions
+               if (!osu.passivated)
+               {
+                  proxy_.evictSession(realId, osu.owner);
+               }
+            }
+            else
+            {
+               proxy_.removeSessionLocal(realId, false);           
+            }
+         }
+         catch (Exception e)
+         {
+            // Not as big a problem; we don't own the session
+            log_.debug("Problem " + action + " session " + realId + " -- " + e);
+         }
+         it.remove(); 
+      }
+   }
    
-   public PojoCache getPojoCache()
+   // ------------------------------------------------------  Lifecyle Embedded
+   
+   /**
+    * Start this Manager when running embedded in JBoss AS.
+    *
+    * @throws org.apache.catalina.LifecycleException
+    */
+   private void startEmbedded() throws LifecycleException
    {
-      return pojoCache_;
+      super.start();
+      
+      // Start the JBossCacheService
+      // Will need to pass the classloader that is associated with this 
+      // web app so de-serialization will work correctly.
+      tcl_ = super.getContainer().getLoader().getClassLoader();
+      
+      try
+      {
+         if (proxy_ == null) // Could happen during a restart
+         {
+            initCacheProxy();
+         }
+         
+         proxy_.start(tcl_, this);
+
+         tm = proxy_.getTransactionManager();
+         if(tm == null)
+         {
+            throw new LifecycleException("JBossCacheManager.start(): Obtain null tm");
+         }
+         
+         initializeUnloadedSessions();
+         
+         // Setup our SnapshotManager
+         initSnapshotManager();
+         
+         // Add SnapshotValve and, if needed, JvmRouteValve and batch repl valve
+         installValves();
+
+         log_.debug("start(): JBossCacheService started");         
+      }
+      catch (LifecycleException le)
+      {
+         throw le;
+      }
+      catch (Exception e)
+      {
+         log_.error("Unable to start manager.", e);
+         throw new LifecycleException(e);
+      }
    }
+
+   // -------------------------------------------------------------------- Misc
    
-   public Cache getPlainCache()
+   private void endTransaction(String id)
    {
-      return plainCache_;
+      if (tm == null)
+      {
+         log_.warn("JBossCacheManager.endTransaction(): tm is null for id: " +id);
+         return;
+      }
+
+      try
+      {
+         if(tm.getTransaction().getStatus() != Status.STATUS_MARKED_ROLLBACK)
+         {
+            tm.commit();
+         }
+         else
+         {
+            log_.info("JBossCacheManager.endTransaction(): rolling back tx for id: " +id);
+            tm.rollback();
+         }
+      }
+      catch (RollbackException re)
+      {
+         // Do nothing here since cache may rollback automatically.
+         log_.warn("JBossCacheManager.endTransaction(): rolling back transaction with exception: " +re);
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException("JBossCacheManager.endTransaction(): Exception for id: " +id, e);
+      }
    }
 
-   public String getCacheConfigName()
+   /**
+    * Gets the session id with any jvmRoute removed.
+    * 
+    * @param id a session id with or without an appended jvmRoute.
+    *           Cannot be <code>null</code>.
+    */
+   private String getRealId(String id)
    {
-      return (cacheFromCacheManager_ ? cacheConfigName_ : null);
+      return (getUseJK() ? Util.getRealId(id) : id);
    }
    
+   private String reportSessionIds(Set ids)
+   {
+      StringBuffer sb = new StringBuffer();
+      boolean added = false;
+      for (Iterator it = ids.iterator(); it.hasNext(); )
+      {
+         if (added)
+         {
+            sb.append(',');
+         }
+         else
+         {
+            added = true;
+         }
+         
+         sb.append(it.next());
+      }
+      return sb.toString();
+   }   
+   
+   // ------------------------------------------------------------ Inner Classes
+   
    private class OwnedSessionUpdate
    {
       String owner;

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManagerMBean.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManagerMBean.java	2008-08-25 15:10:56 UTC (rev 77438)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossCacheManagerMBean.java	2008-08-25 15:16:30 UTC (rev 77439)
@@ -34,18 +34,12 @@
     * 
     * @param sessionId the id of the session
     * @param key       the attribute key
-    * @return          the value, or <code>null</code> if the session or
-    *                  key does not exist.
+    * @return          the value, converted to a String via toString(), 
+    *                  or <code>null</code> if the session or key does not exist.
     */
-   Object getSessionAttribute(String sessionId, String key);
+   String getSessionAttribute(String sessionId, String key);
    
    /**
-    * Same as <code>getSessionAttribute(sessionId, key).toString()</code>.
-    * 
-    */
-   String getSessionAttributeString(String sessionId, String key);
-   
-   /**
     * Expires the given session. If the session is in the distributed store 
     * but hasn't been loaded on this node, invoking this method will cause it 
     * to be loaded.
@@ -55,17 +49,28 @@
    void expireSession(String sessionId);
    
    /**
-    * Gets the last time the given session was accessed on this node.
-    * Information about sessions stored in the distributed store but never
-    * accessed on this node will not be made available.
+    * Gets the last time the given session was accessed.  If the session is in 
+    * the distributed store but hasn't been loaded on this node, invoking this 
+    * method will cause it to be loaded.
     * 
     * @param sessionId
-    * @return the last accessed time, or <code>null</code> if the session
-    *         has expired or has never been accessed on this node.
+    * @return the last accessed time, or the empty string if the session
+    *         doesn't exist.
     */
    String getLastAccessedTime(String sessionId);
    
    /**
+    * Gets the creation time of the given session.  If the session is in 
+    * the distributed store but hasn't been loaded on this node, invoking this 
+    * method will cause it to be loaded.
+    * 
+    * @param sessionId
+    * @return the creation time, or or the empty string if the session
+    *         doesn't exist.
+    */
+   String getCreationTime(String sessionId);
+   
+   /**
     * Gets the cache config name used to get the underlying cache
     * from a cache manager.
     * 
@@ -174,17 +179,6 @@
    String listLocalSessionIds();
    
    /**
-    * Gets the count of sessions known to this manager, excluding those
-    * in the distributed stored that have not been accessed on this node.
-    */
-   long getLocalActiveSessionCount();
-   
-   /**
-    * Gets the highest value seen for {@link #getLocalSessionCount()}
-    */
-   long getMaxLocalActiveSessionCount();
-   
-   /**
     * Gets whether passivation was enabled in jboss-web.xml and in the
     * underlying cache.
     * 
@@ -225,4 +219,9 @@
     * @return
     */
    long getPassivationMinIdleTime();
+   
+   /**
+    * Gets the number of duplicated session ids generated.
+    */
+   int getDuplicates();
 }

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossManager.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossManager.java	2008-08-25 15:10:56 UTC (rev 77438)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossManager.java	2008-08-25 15:16:30 UTC (rev 77439)
@@ -25,11 +25,14 @@
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 
 import javax.management.MBeanServer;
 import javax.management.ObjectName;
@@ -40,19 +43,18 @@
 import org.apache.catalina.Context;
 import org.apache.catalina.Engine;
 import org.apache.catalina.Globals;
+import org.apache.catalina.Host;
 import org.apache.catalina.Lifecycle;
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.LifecycleListener;
-import org.apache.catalina.Session;
 import org.apache.catalina.connector.Connector;
 import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.ContainerBase;
 import org.apache.catalina.util.LifecycleSupport;
+import org.apache.tomcat.util.modeler.Registry;
 import org.jboss.logging.Logger;
 import org.jboss.metadata.web.jboss.JBossWebMetaData;
-import org.jboss.metadata.web.jboss.ReplicationConfig;
 import org.jboss.metadata.web.jboss.PassivationConfig;
-import org.jboss.metadata.web.jboss.ReplicationTrigger;
-import org.jboss.mx.util.MBeanServerLocator;
 import org.jboss.web.tomcat.statistics.ReplicationStatistics;
 
 
@@ -62,25 +64,17 @@
  *
  * @author Ben Wang
  * @author Hany Mesha
+ * @author Brian Stansberry
+ * 
  * @version $Revision: 60686 $
  */
 public abstract class JBossManager
    implements AbstractJBossManager, Lifecycle,
       JBossManagerMBean, PropertyChangeListener
-{  
-   // -- Constants ----------------------------------------
-   /**
-    * Informational name for this Catalina component
-    */
-   private static final String info_ = "JBossManager/1.0";
-
-   // -- Class attributes ---------------------------------
+{
+   // ------------------------------------------------------------------ Fields
+   
    protected ReplicationStatistics stats_ = new ReplicationStatistics();
-
-   /**
-    * Policy to determine if a session is dirty
-    */
-   protected ReplicationTrigger replicationTrigger_;
    
    /**
     * Session passivation flag set in jboss-web.xml by the user.
@@ -92,7 +86,7 @@
    /**
     * Min time (milliseconds) the session must be idle since lastAccesstime before 
     * it's eligible for passivation if passivation is enabled and more
-    * than maxActive_ sessions are in memory.
+    * than maxActiveAllowed_ sessions are in memory.
     * Setting to -1 means it's ignored.
     */
    protected int passivationMinIdleTime_ = -1;
@@ -108,27 +102,39 @@
     * The lifecycle_ event support for this component.
     */
    protected LifecycleSupport lifecycle_ = new LifecycleSupport(this);
+   
    /**
     * Has this component been started_ yet?
     */
    protected boolean started_ = false;
+   
    /**
     * Are we allowing backgroundProcess() to execute? We use an object
     * so stop() can lock on it to wait for
     */
    protected AtomicBoolean backgroundProcessAllowed = new AtomicBoolean();
+   
    /**
     * The objectname this Manager is associated with
     */
    protected ObjectName objectName_;
+   
    /**
-    * The Log-object for this class
+    * The Log object for this class
     */
-   protected Logger log_ = Logger.getLogger(this.getClass().getName());
+   protected Logger log_ = Logger.getLogger(this.getClass().getName());  
+   
    /**
+    * Whether trace logging is enabled for our logger. Rechecked
+    * every time backgroundProcess() is invoked.
+    */
+   protected boolean trace_ = log_.isTraceEnabled();   
+   
+   /**
     * The Container with which this Manager is associated.
     */
    protected Container container_;
+   
   /**
    /**
     * The distributable flag for Sessions created by this Manager.  If this
@@ -136,80 +142,105 @@
     * session controlled by this Manager must be Serializable.
     */
    protected boolean distributable_ = true;
+   
    /**
     * The default maximum inactive interval for Sessions created by
     * this Manager.
     */
    protected int maxInactiveInterval_ = 60;
-   /**
-    * The session id length of Sessions created by this Manager.
-    */
-   protected int sessionIdLength_ = 16;
 
-   // Maximum of active sessions allowed. -1 is unlimited.
-   protected int maxActive_ = -1;
+   /** Maximum of active sessions allowed. -1 is unlimited. */
+   protected int maxActiveAllowed_ = -1;
 
-   // Number of sessions created by this manager
-   protected int createdCounter_ = 0;
+   /** Number of sessions created by this manager */
+   protected AtomicInteger createdCounter_ = new AtomicInteger();
 
-   // number of rejected sessions because the number active sessions exceeds maxActive
-   protected volatile int rejectedCounter_ = 0;
+   /** number of sessions rejected because the number active sessions exceeds maxActive */
+   protected AtomicInteger rejectedCounter_ = new AtomicInteger();
 
-   // Number of active sessions
-   protected int activeCounter_ = 0;
+   /** Number of active sessions */
+   protected AtomicInteger localActiveCounter_ = new AtomicInteger();
+   
+   /** Maximum number of concurrently locally active sessions */
+   protected AtomicInteger maxLocalActiveCounter_ = new AtomicInteger();
 
-   // Maximum number of active sessions seen so far
-   protected int maxActiveCounter_ = 0;
+   /** Maximum number of active sessions seen so far */
+   protected AtomicInteger maxActiveCounter_ = new AtomicInteger();
 
-   // number of expired session ids. Not sure what exactly does it mean in our clustered case.
-   protected volatile int expiredCounter_ = 0;
+   /** Number of sessions that have been active locally that are now expired. */
+   protected AtomicInteger expiredCounter_ = new AtomicInteger();
 
+   /** Number of ms since last call to reset() */
    protected long timeSinceLastReset_ = 0;
 
-   // Cumulative time spent in backgroundProcess
-   protected long processingTime_ = 0;
+   /** Cumulative time spent in backgroundProcess */
+   protected AtomicLong processingTime_ = new AtomicLong();
    
-   /**
-    * Map<String,ClusteredSession>. Store the local sessions.
-    */
-   protected final Map sessions_ = new ConcurrentHashMap();
+   /** Stores the locally active sessions. */
+   protected final Map<String, ClusteredSession> sessions_ = new ConcurrentHashMap<String,ClusteredSession>();
 
-   /**
-    * If set to true, it will not replicate the access time stamp unless attributes are dirty.
-    * 
-    * @deprecated unused
-    */
-   protected boolean useLocalCache_ = true;
-
-   /**
-    * The property change support for this component.
-    */
+   /** The property change support for this component. */
    protected PropertyChangeSupport support_ = new PropertyChangeSupport(this);
 
-   protected SessionIDGenerator sessionIDGenerator_;
+   /** Generates ids for new sessions */
+   protected SessionIDGenerator sessionIDGenerator_= SessionIDGenerator.getInstance();;
 
+   /** Our containing engine's jvmRoute (if it has one) */
    protected String jvmRoute_;
+
+   /** Our JMX Server */
+   protected MBeanServer mserver_ = null;
    
-   // TODO Need a string manager to handle exception localization
+   /** 
+    * How often calls to backgroundProcess() should trigger 
+    * expiration/passivation processing
+    */
+   protected volatile int processExpiresFrequency = 1;
+   
+   /**
+    * How many times backgroundProcess() has been called since we last
+    * processed expiration/passivation.
+    */
+   protected int backgroundProcessCount = 0;
+   
+   /** Maximum time in ms a now expired session has been alive */
+   protected AtomicInteger maxAliveTime = new AtomicInteger();
+   
+   /** Average time in ms a now expired session has been alive */
+   protected AtomicInteger averageAliveTime = new AtomicInteger();
+   
+   /** 
+    * Number of times our session id generator has generated an id
+    * that matches an existing session.
+    */
+   protected AtomicInteger duplicates_ = new AtomicInteger(); 
+   
+   // TODO Need a string manager to handle localization
 
-   public JBossManager()
+   // ------------------------------------------------------------ Constructors
+   
+   /**
+    * Creates a new JBossManager
+    */
+   protected JBossManager()
    {
-      sessionIDGenerator_ = SessionIDGenerator.getInstance();
-
    }
 
+   // -------------------------------------------------------------- Properties
+   
+   // ---------------------------------------------------- AbstractJBossManager
+
+   /**
+    * {@inheritDoc}
+    */
    public void init(String name, JBossWebMetaData webMetaData)
            throws ClusteringNotSupportedException
-   {
-      ReplicationConfig rpc = webMetaData.getReplicationConfig();
-      if (rpc != null)
-      {         
-         replicationTrigger_ = rpc.getReplicationTrigger();
-      }      
+   {     
       if (webMetaData.getMaxActiveSessions() != null)
       {
-         maxActive_ = webMetaData.getMaxActiveSessions().intValue();
+         maxActiveAllowed_ = webMetaData.getMaxActiveSessions().intValue();
       }
+      
       PassivationConfig pConfig = webMetaData.getPassivationConfig();
       if (pConfig != null)
       {
@@ -228,213 +259,481 @@
          }
       }
       
-      log_.debug("init(): replicationTrigger is " + replicationTrigger_ +
-         " and maxActiveSessions allowed is " + maxActive_ +
+      log_.debug("init(): maxActiveSessions allowed is " + maxActiveAllowed_ +
          " and passivationMode is " + passivationMode_);
+   }
 
-      try
+   /**
+    * {@inheritDoc}
+    */
+   public String getJvmRoute()
+   {
+      if (jvmRoute_ == null)
       {
-         // Give this manager a name
-         objectName_ = new ObjectName("jboss.web:service=ClusterManager,WebModule=" + name);
+         Engine e = getEngine();
+         jvmRoute_= (e == null ? null : e.getJvmRoute());
       }
-      catch (Throwable e)
+      return jvmRoute_;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public void setNewSessionCookie(String sessionId, HttpServletResponse response)
+   {
+      if (response != null)
       {
-         log_.error("Could not create ObjectName", e);
-         throw new ClusteringNotSupportedException(e.toString());
+         Context context = (Context) container_;
+         Connector connector = ((Response)response).getConnector();
+         if (context.getCookies())
+         {
+            // set a new session cookie
+            Cookie newCookie = new Cookie(Globals.SESSION_COOKIE_NAME, sessionId);
+            if (trace_)
+            {
+               log_.trace("Setting cookie with session id:" + sessionId + " & name:" + Globals.SESSION_COOKIE_NAME);
+            }
+            newCookie.setPath("/");
+            newCookie.setMaxAge(-1);
+            if (connector.getSecure()) {
+                newCookie.setSecure(true);
+            }
+
+            response.addCookie(newCookie);
+         }
       }
    }
+   
+   // ----------------------------------------------------------------- Manager
 
-   public ReplicationTrigger getReplicationTrigger()
+   /**
+    * {@inheritDoc}
+    */
+   public void addPropertyChangeListener(PropertyChangeListener listener)
    {
-      return this.replicationTrigger_;
+      support_.addPropertyChangeListener(listener);
    }
 
    /**
-    * Sets the type of operations on a <code>HttpSession</code> that
-    * trigger replication.  Valid values are:
-    * <ul>
-    * <li>SET_AND_GET</li>
-    * <li>SET_AND_NON_PRIMITIVE_GET</li>
-    * <li>SET</li>
-    * </ul>
+    * {@inheritDoc}
     */
-   public void setReplicationTrigger(ReplicationTrigger trigger)
+   public void removePropertyChangeListener(PropertyChangeListener listener)
    {
-      this.replicationTrigger_ = trigger;
+      support_.removePropertyChangeListener(listener);
    }
 
    /**
-    * Retrieve the enclosing Engine for this Manager.
-    *
-    * @return an Engine object (or null).
+    * {@inheritDoc}
     */
-   public Engine getEngine()
+   public void propertyChange(PropertyChangeEvent evt)
    {
-      Engine e = null;
-      for (Container c = getContainer(); e == null && c != null; c = c.getParent())
+      support_.firePropertyChange(evt);
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public void backgroundProcess()
+   {
+      // Always reset trace_
+      trace_ = log_.isTraceEnabled();
+      
+      // For other work, only execute every processExpiresFrequency
+      backgroundProcessCount = (backgroundProcessCount + 1) % processExpiresFrequency;
+      if (backgroundProcessCount != 0)
+         return;
+      
+      synchronized (backgroundProcessAllowed)
       {
-         if (c != null && c instanceof Engine)
+         if (backgroundProcessAllowed.get())
          {
-            e = (Engine) c;
+            long start = System.currentTimeMillis();
+            
+            processExpirationPassivation();
+            
+            long elapsed = System.currentTimeMillis() - start;
+            
+            processingTime_.addAndGet(elapsed);
          }
       }
-      return e;
    }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public int getActiveSessions()
+   {
+      return calcActiveSessions();
+   }
 
    /**
-    * Retrieve the JvmRoute for the enclosing Engine.
-    *
-    * @return the JvmRoute or null.
+    * {@inheritDoc}
     */
-   public String getJvmRoute()
+   public Container getContainer()
    {
-      if (jvmRoute_ == null)
+      return container_;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public void setContainer(Container container)
+   {
+      // De-register from the old Container (if any)
+      if ((this.container_ != null) && (this.container_ instanceof Context))
+         this.container_.removePropertyChangeListener(this);
+
+      // Default processing provided by our superclass
+      this.container_ = container;
+
+      // Register with the new Container (if any)
+      if ((this.container_ != null) && (this.container_ instanceof Context))
       {
-         Engine e = getEngine();
-         jvmRoute_= (e == null ? null : e.getJvmRoute());
+         setMaxInactiveInterval
+            (((Context) this.container_).getSessionTimeout() * 60);
+         this.container_.addPropertyChangeListener(this);
       }
-      return jvmRoute_;
    }
 
    /**
-    * Get a new session-id from the distributed store
-    *
-    * @return new session-id
+    * {@inheritDoc}
     */
-   protected String getNextId()
+   public boolean getDistributable()
    {
-      return sessionIDGenerator_.getSessionId();
+      return distributable_;
    }
 
    /**
-    * Gets the JMX <code>ObjectName</code> under
-    * which our <code>TreeCache</code> is registered. 
+    * {@inheritDoc}
     */
-   public ObjectName getObjectName()
+   public void setDistributable(boolean distributable)
    {
-      return objectName_;
+      this.distributable_ = distributable;
    }
 
-   public boolean isUseLocalCache()
+   /**
+    * {@inheritDoc}
+    */
+   public int getExpiredSessions()
    {
-      return useLocalCache_;
+      return expiredCounter_.get();
    }
 
+   /** No-op */
+   public void setExpiredSessions(int expiredSessions)
+   {
+      // ignored
+   }
+
    /**
-    * Sets a new cookie for the given session id and response
-    *
-    * @param sessionId The session id
+    * {@inheritDoc}
     */
-   public void setSessionCookie(String sessionId)
+   public int getMaxActive()
    {
-      HttpServletResponse response = SessionReplicationContext.getOriginalResponse();
-      setNewSessionCookie(sessionId, response);
+      return maxActiveAllowed_;
    }
 
-   public void setNewSessionCookie(String sessionId, HttpServletResponse response)
+   /**
+    * {@inheritDoc}
+    */
+   public void setMaxActive(int maxActive)
    {
-      if (response != null)
-      {
-         Context context = (Context) container_;
-         Connector connector = ((Response)response).getConnector();
-         if (context.getCookies())
-         {
-            // set a new session cookie
-            Cookie newCookie = new Cookie(Globals.SESSION_COOKIE_NAME, sessionId);
-            if (log_.isTraceEnabled())
-            {
-               log_.trace("Setting cookie with session id:" + sessionId + " & name:" + Globals.SESSION_COOKIE_NAME);
-            }
-            newCookie.setPath("/");
-            newCookie.setMaxAge(-1);
-            if (connector.getSecure()) {
-                newCookie.setSecure(true);
-            }
+      this.maxActiveAllowed_ = maxActive;
+   }
 
-            response.addCookie(newCookie);
-         }
-      }
+   /**
+    * {@inheritDoc}
+    */
+   public int getMaxInactiveInterval()
+   {
+      return maxInactiveInterval_;
    }
 
-   // JBossManagerMBean-methods -------------------------------------
-
-   // A better property name for the MBean API
-   public int getMaxActiveAllowed()
+   /**
+    * {@inheritDoc}
+    */
+   public void setMaxInactiveInterval(int interval)
    {
-      return getMaxActive();
+      this.maxInactiveInterval_ = interval;
    }
    
-   // A better property name for the MBean API
-   public void setMaxActiveAllowed(int maxActive)
+   /**
+    * {@inheritDoc}
+    */
+   public long getProcessingTime()
    {
-      setMaxActive(maxActive);
+      return this.processingTime_.get();
    }
+
+   /**
+    * {@inheritDoc}
+    */
+   public int getRejectedSessions()
+   {
+      return rejectedCounter_.get();
+   }
+
+   /** No-op */
+   public void setRejectedSessions(int rejectedSessions)
+   {
+      // ignored
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public int getSessionAverageAliveTime()
+   {
+       return averageAliveTime.get();
+   }
+
+   /** No-op */
+   public void setSessionAverageAliveTime(int sessionAverageAliveTime)
+   {
+      // ignored
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public int getSessionCounter()
+   {
+      return createdCounter_.get();
+   }
+
+   /** No-op */
+   public void setSessionCounter(int sessionCounter)
+   {
+      // ignored
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public int getSessionIdLength()
+   {
+      return SessionIDGenerator.SESSION_ID_BYTES;
+   }
+
+   /** No-op */
+   public void setSessionIdLength(int idLength)
+   {
+      // ignored
+   }
    
-   public long getMaxActiveSessionCount()
+   /**
+    * {@inheritDoc}
+    */
+   public int getSessionMaxAliveTime()
    {
-      return this.maxActiveCounter_;
+       return maxAliveTime.get();
    }
 
-   public ReplicationStatistics getReplicationStatistics()
+   /** No-op */
+   public void setSessionMaxAliveTime(int sessionAliveTime)
    {
-      return stats_;
+      // ignored
    }
 
-   public void resetStats()
+   /** Throws UnsupportedOperationException */
+   public void load() throws ClassNotFoundException, IOException
    {
-      stats_.resetStats();
-      activeCounter_ = 0;
-      maxActiveCounter_ = 0;
-      rejectedCounter_ = 0;
-      createdCounter_ = 0;
-      expiredCounter_ = 0;
-      processingTime_ = 0;
-      timeSinceLastReset_ = System.currentTimeMillis();
+      throw new UnsupportedOperationException("load() not supported");
    }
 
-   public long timeInSecondsSinceLastReset()
+   /** Throws UnsupportedOperationException */
+   public void unload() throws IOException
    {
-      return (System.currentTimeMillis() - timeSinceLastReset_) / (1000L);
+      throw new UnsupportedOperationException("unload() not supported");
    }
 
+   // -------------------------------------------------------------- Lifecycle
+
+   public void addLifecycleListener(LifecycleListener listener)
+   {
+      lifecycle_.addLifecycleListener(listener);
+   }
+
+   public LifecycleListener[] findLifecycleListeners()
+   {
+      return lifecycle_.findLifecycleListeners();
+   }
+
+   public void removeLifecycleListener(LifecycleListener listener)
+   {
+      lifecycle_.removeLifecycleListener(listener);
+   }
+
+   /**
+    * Start this Manager
+    *
+    * @throws org.apache.catalina.LifecycleException
+    *
+    */
+   public void start() throws LifecycleException
+   {
+      startManager();
+   }
+
+   /**
+    * Stop this Manager
+    *
+    * @throws org.apache.catalina.LifecycleException
+    *
+    */
+   public void stop() throws LifecycleException
+   {
+      // Block for any ongoing backgroundProcess, then disable
+      synchronized (backgroundProcessAllowed)
+      {
+         backgroundProcessAllowed.set(false);
+      }
+      
+      resetStats();
+      stopManager();
+   }
+
+   // ------------------------------------------------------- JBossManagerMBean
+
+   /**
+    * {@inheritDoc}
+    */
    public long getActiveSessionCount()
    {
-      return getActiveSessions();
+      return calcActiveSessions();
    }
 
+   /**
+    * {@inheritDoc}
+    */
+   public String getAlgorithm()
+   {
+      return SessionIDGenerator.SESSION_ID_HASH_ALGORITHM;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public String getClassName()
+   {
+      return getClass().getName();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
    public long getCreatedSessionCount()
    {
-      return createdCounter_;
+      return createdCounter_.get();
    }
 
+   /**
+    * {@inheritDoc}
+    */
    public long getExpiredSessionCount()
    {
-      return expiredCounter_;
+      return expiredCounter_.get();
    }
 
-   public long getRejectedSessionCount()
+   /**
+    * {@inheritDoc}
+    */
+   public long getLocalActiveSessionCount()
    {
-      return rejectedCounter_;
+      return localActiveCounter_.get();
    }
 
-   public int getSessionMaxAliveTime()
+   /**
+    * {@inheritDoc}
+    */
+   public int getMaxActiveAllowed()
    {
-       return 0;
+      return getMaxActive();
    }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public void setMaxActiveAllowed(int maxActive)
+   {
+      setMaxActive(maxActive);
+   }
 
-   public void setSessionMaxAliveTime(int sessionMaxAliveTime)
+   /**
+    * {@inheritDoc}
+    */
+   public int getMaxActiveSessions()
    {
+      return getMaxActiveAllowed();
    }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public long getMaxActiveSessionCount()
+   {
+      return this.maxActiveCounter_.get();
+   }
 
-   public int getSessionAverageAliveTime()
+   /**
+    * {@inheritDoc}
+    */
+   public long getMaxLocalActiveSessionCount()
    {
-       return 0;
+      return maxLocalActiveCounter_.get();
    }
 
-   public void setSessionAverageAliveTime(int sessionAverageAliveTime)
+   /**
+    * {@inheritDoc}
+    */
+   public String getName()
    {
+      return getClass().getSimpleName();
    }
 
+   /**
+    * {@inheritDoc}
+    */
+   public int getProcessExpiresFrequency()
+   {
+      return this.processExpiresFrequency;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public void setProcessExpiresFrequency(int frequency)
+   {
+      this.processExpiresFrequency = frequency;      
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public long getRejectedSessionCount()
+   {
+      return rejectedCounter_.get();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public ReplicationStatistics getReplicationStatistics()
+   {
+      return stats_;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public long getTimeSinceLastReset()
+   {
+      return (System.currentTimeMillis() - timeSinceLastReset_) / (1000L);
+   }
+
+   /**
+    * {@inheritDoc}
+    */
    public String reportReplicationStatistics()
    {
       StringBuffer tmp = new StringBuffer();
@@ -492,6 +791,9 @@
 
    }
    
+   /**
+    * {@inheritDoc}
+    */
    public String reportReplicationStatisticsCSV()
    {
       StringBuffer tmp = createCSVHeader();
@@ -534,6 +836,9 @@
 
    }
    
+   /**
+    * {@inheritDoc}
+    */
    public String reportReplicationStatisticsCSV(String sessionId)
    {
       StringBuffer tmp = createCSVHeader();
@@ -569,73 +874,226 @@
       }
       return tmp.toString();
    }
-   
-   private StringBuffer createCSVHeader()
+
+   /**
+    * {@inheritDoc}
+    */
+   public void resetStats()
    {
-      StringBuffer tmp = new StringBuffer();
-      tmp.append("sessionID,");
-      tmp.append("replicationCount,");
-      tmp.append("minPassivationTime,");
-      tmp.append("maxPassivationTime,");
-      tmp.append("totalPassivationTime,");
-      tmp.append("minReplicationTime,");
-      tmp.append("maxReplicationTime,");
-      tmp.append("totalReplicationlTime,");
-      tmp.append("loadCount,");
-      tmp.append("minLoadTime,");
-      tmp.append("maxLoadTime,");
-      tmp.append("totalLoadTime");
-      
-      return tmp;
+      stats_.resetStats();
+      maxActiveCounter_.set(localActiveCounter_.get());
+      rejectedCounter_.set(0);
+      createdCounter_.set(0);
+      expiredCounter_.set(0);
+      processingTime_.set(0);
+      maxAliveTime.set(0);
+      averageAliveTime.set(0);
+      duplicates_.set(0);
+      timeSinceLastReset_ = System.currentTimeMillis();
    }
 
-   // Lifecycle-methods -------------------------------------
+   // ------------------------------------------------------------------ Public
 
-   public void addLifecycleListener(LifecycleListener listener)
+   /**
+    * Gets the JMX <code>ObjectName</code> under
+    * which our <code>TreeCache</code> is registered. 
+    */
+   public ObjectName getObjectName()
    {
-      lifecycle_.addLifecycleListener(listener);
+      return objectName_;
    }
 
-   public LifecycleListener[] findLifecycleListeners()
-   {
-      return lifecycle_.findLifecycleListeners();
-   }
+   // ------------------------------------------------------------------ Protected
 
-   public void removeLifecycleListener(LifecycleListener listener)
+   /**
+    * Go through all sessions and look if they have expired or need to be passivated.
+    */
+   protected abstract void processExpirationPassivation(); 
+   
+   /** Get the total number of active sessions */
+   protected abstract int getTotalActiveSessions();
+   
+   /** 
+    * Calculates the number of active sessions, and updates
+    * the max # of local active sessions and max # of sessions.
+    * <p>
+    * Call this method when a new session is added or when an
+    * accurate count of active sessions is needed.
+    * </p>
+    * 
+    * @return the size of the sessions map + the size of the unloaded sessions 
+    *         map - the count of passivated sessions
+    */
+   protected int calcActiveSessions()
    {
-      lifecycle_.removeLifecycleListener(listener);
+      localActiveCounter_.set(sessions_.size());
+      int active = localActiveCounter_.get();
+      int maxLocal = maxLocalActiveCounter_.get();
+      while (active > maxLocal)
+      {
+         if (!maxLocalActiveCounter_.compareAndSet(maxLocal, active))
+         {
+            maxLocal = maxLocalActiveCounter_.get();
+         }
+      }
+      
+      int count = getTotalActiveSessions();
+      int max = maxActiveCounter_.get();
+      while (count > max)
+      {
+         if (!maxActiveCounter_.compareAndSet(max, count))
+         {
+            max = maxActiveCounter_.get();
+            // Something changed, so reset our count
+            count = getTotalActiveSessions();
+         }
+      }
+      return count;
    }
 
    /**
-    * Start this Manager
+    * Returns the given session if it is being actively managed by this manager.
+    * An actively managed session is on that was either created on this server,
+    * brought into local management by a call to
+    * {@link #findLocalSession(String)} or brought into local management by a
+    * call to {@link #findSessions()}.
     *
-    * @throws org.apache.catalina.LifecycleException
+    * @param realId the session id, with any trailing jvmRoute removed.
     *
+    * @see #getRealId(String)
     */
-   public void start() throws LifecycleException
+   protected ClusteredSession findLocalSession(String realId)
    {
-      startManager();
+      return sessions_.get(realId);
    }
 
    /**
-    * Stop this Manager
+    * Returns all the sessions that are being actively managed by this manager.
+    * This includes those that were created on this server, those that were
+    * brought into local management by a call to
+    * {@link #findLocalSession(String)} as well as all sessions brought into
+    * local management by a call to {@link #findSessions()}.
+    */
+   protected ClusteredSession[] findLocalSessions()
+   {
+      Collection<ClusteredSession> coll = sessions_.values();
+      ClusteredSession[] sess = new ClusteredSession[coll.size()];
+      return coll.toArray(sess);
+   }
+
+   /**
+    * Get a new session-id from the distributed store
     *
-    * @throws org.apache.catalina.LifecycleException
-    *
+    * @return new session-id
     */
-   public void stop() throws LifecycleException
+   protected String getNextId()
    {
-      // Block for any ongoing backgroundProcess, then disable
-      synchronized (backgroundProcessAllowed)
+      return sessionIDGenerator_.getSessionId();
+   }
+   
+   /**
+    * Updates statistics to reflect that a session with a given "alive time"
+    * has been expired.
+    * 
+    * @param sessionAliveTime number of ms from when the session was created
+    *                         to when it was expired.
+    */
+   protected void sessionExpired(int sessionAliveTime)
+   {
+      int current = maxAliveTime.get();
+      while (sessionAliveTime > current)
       {
-         backgroundProcessAllowed.set(false);
+         if (maxAliveTime.compareAndSet(current, sessionAliveTime))
+            break;
+         else
+            current = maxAliveTime.get();
       }
       
-      resetStats();
-      stopManager();
+      expiredCounter_.incrementAndGet();
+      int newAverage;
+      do
+      {
+         int expCount = expiredCounter_.get();
+         current = averageAliveTime.get();
+         newAverage = ((current * (expCount - 1)) + sessionAliveTime)/expCount;
+      }
+      while (averageAliveTime.compareAndSet(current, newAverage) == false);
    }
+   
+   /**
+    * Register this Manager with JMX.
+    */
+   protected void registerManagerMBean() 
+   {
+      try
+      {
+         MBeanServer server = getMBeanServer();
 
+         String domain;
+         if (container_ instanceof ContainerBase)
+         {
+            domain = ((ContainerBase) container_).getDomain();
+         }
+         else
+         {
+            domain = server.getDefaultDomain();
+         }
+         String hostName = ((Host) container_.getParent()).getName();
+         hostName = (hostName == null) ? "localhost" : hostName;
+         ObjectName clusterName = new ObjectName(domain
+               + ":type=Manager,host=" + hostName + ",path="
+               + ((Context) container_).getPath());
+
+         if (server.isRegistered(clusterName))
+         {
+            log_.warn("MBean " + clusterName + " already registered");
+            return;
+         }
+
+         objectName_ = clusterName;
+         server.registerMBean(this, clusterName);
+
+      }
+      catch (Exception ex)
+      {
+         log_.error("Could not register " + getClass().getSimpleName() + " to MBeanServer", ex);
+      }
+   }
+
    /**
+    * Unregister this Manager from the JMX server.
+    */
+   protected void unregisterManagerMBean()
+   {
+      if (mserver_ != null)
+      {
+         try
+         {
+            mserver_.unregisterMBean(objectName_);
+         }
+         catch (Exception e)
+         {
+            log_.error("Could not unregister " + getClass().getSimpleName() + " from MBeanServer", e);
+         }
+      }
+   }
+
+   /**
+    * Get the current MBean Server.
+    * 
+    * @return
+    * @throws Exception
+    */
+   protected MBeanServer getMBeanServer() throws Exception
+   {
+      if (mserver_ == null)
+      {
+         mserver_ = Registry.getRegistry(null, null).getMBeanServer();
+      }
+      return mserver_;
+   }
+
+   /**
     * Prepare for the beginning of active use of the public methods of this
     * component.  This method should be called after <code>configure()</code>,
     * and before any of the public methods of the component are utilized.
@@ -660,15 +1118,7 @@
       started_ = true;
 
       // register ClusterManagerMBean to the MBeanServer
-      try
-      {
-         MBeanServer server = MBeanServerLocator.locateJBoss();
-         server.registerMBean(this, objectName_);
-      }
-      catch (Exception e)
-      {
-         log_.error("Could not register ClusterManagerMBean to MBeanServer", e);
-      }
+      registerManagerMBean();
    }
 
    /**
@@ -692,236 +1142,47 @@
       lifecycle_.fireLifecycleEvent(STOP_EVENT, null);
       started_ = false;
 
-      // unregister ClusterManagerMBean from the MBeanServer
-      try
-      {
-         MBeanServer server = MBeanServerLocator.locateJBoss();
-         server.unregisterMBean(objectName_);
-      }
-      catch (Exception e)
-      {
-         log_.error("Could not unregister ClusterManagerMBean from MBeanServer", e);
-      }
+      // unregister from the MBeanServer
+      unregisterManagerMBean();
    }
 
-   // Manager-methods -------------------------------------
-   public Container getContainer()
-   {
-      return container_;
-   }
-
-   public void setContainer(Container container)
-   {
-      // De-register from the old Container (if any)
-      if ((this.container_ != null) && (this.container_ instanceof Context))
-         this.container_.removePropertyChangeListener(this);
-
-      // Default processing provided by our superclass
-      this.container_ = container;
-
-      // Register with the new Container (if any)
-      if ((this.container_ != null) && (this.container_ instanceof Context))
-      {
-         setMaxInactiveInterval
-            (((Context) this.container_).getSessionTimeout() * 60);
-         this.container_.addPropertyChangeListener(this);
-      }
-   }
-
-   public boolean getDistributable()
-   {
-      return distributable_;
-   }
-
-   public void setDistributable(boolean distributable)
-   {
-      this.distributable_ = distributable;
-   }
-
-   public String getInfo()
-   {
-      return info_;
-   }
-
-   public int getMaxInactiveInterval()
-   {
-      return maxInactiveInterval_;
-   }
-
-   public void setMaxInactiveInterval(int interval)
-   {
-      this.maxInactiveInterval_ = interval;
-   }
-
-   public int getSessionIdLength()
-   {
-      return sessionIdLength_;
-   }
-
-   public void setSessionIdLength(int idLength)
-   {
-      this.sessionIdLength_ = idLength;
-   }
-
-   public int getSessionCounter()
-   {
-      return createdCounter_;
-   }
-
-   public void setSessionCounter(int sessionCounter)
-   {
-      this.createdCounter_ = sessionCounter;
-   }
-
-   public int getMaxActive()
-   {
-      return maxActive_;
-   }
-
-   public void setMaxActive(int maxActive)
-   {
-      this.maxActive_ = maxActive;
-   }
-
-   public int getExpiredSessions()
-   {
-      return expiredCounter_;
-   }
-
-   public void setExpiredSessions(int expiredSessions)
-   {
-      this.expiredCounter_ = expiredSessions;
-   }
-
-   public int getRejectedSessions()
-   {
-      return rejectedCounter_;
-   }
-
-   public void setRejectedSessions(int rejectedSessions)
-   {
-      this.rejectedCounter_ = rejectedSessions;
-   }
+   // ----------------------------------------------------------------- Private
    
-   public long getProcessingTime()
+   private StringBuffer createCSVHeader()
    {
-      return this.processingTime_;
+      StringBuffer tmp = new StringBuffer();
+      tmp.append("sessionID,");
+      tmp.append("replicationCount,");
+      tmp.append("minPassivationTime,");
+      tmp.append("maxPassivationTime,");
+      tmp.append("totalPassivationTime,");
+      tmp.append("minReplicationTime,");
+      tmp.append("maxReplicationTime,");
+      tmp.append("totalReplicationlTime,");
+      tmp.append("loadCount,");
+      tmp.append("minLoadTime,");
+      tmp.append("maxLoadTime,");
+      tmp.append("totalLoadTime");
+      
+      return tmp;
    }
 
-   public void addPropertyChangeListener(PropertyChangeListener listener)
-   {
-      support_.addPropertyChangeListener(listener);
-   }
-
    /**
-    * Remove the active session locally from the manager without replicating to the cluster. This can be
-    * useful when the session is exipred, for example, where there is not need to propagate the expiration.
+    * Retrieve the enclosing Engine for this Manager.
     *
-    * @param session
+    * @return an Engine object (or null).
     */
-   public abstract void removeLocal(Session session);
-
-   /**
-    * Store the modified session.
-    *
-    * @param session
-    */
-   public abstract boolean storeSession(Session session);
-
-   public int getActiveSessions()
+   private Engine getEngine()
    {
-      return activeCounter_;
-   }
-
-/*
-   public void add(Session session)
-   {
-      //To change body of implemented methods use File | Settings | File Templates.
-   }
-
-   public Session createEmptySession()
-   {
-      return null;  //To change body of implemented methods use File | Settings | File Templates.
-   }
-
-   public Session createSession()
-   {
-      return null;  //To change body of implemented methods use File | Settings | File Templates.
-   }
-
-   public Session findSession(String id) throws IOException
-   {
-      return null;  //To change body of implemented methods use File | Settings | File Templates.
-   }
-
-   public Session[] findSessions()
-   {
-      return new Session[0];  //To change body of implemented methods use File | Settings | File Templates.
-   }
-
-   public void remove(Session session)
-   {
-      //To change body of implemented methods use File | Settings | File Templates.
-   }
-*/
-
-   public void load() throws ClassNotFoundException, IOException
-   {
-      // TODO. Implement persistence layer.
-      throw new RuntimeException("JBossManager.load(): Method not implemented.");
-   }
-
-   public void removePropertyChangeListener(PropertyChangeListener listener)
-   {
-      support_.removePropertyChangeListener(listener);
-   }
-
-   public void unload() throws IOException
-   {
-      // TODO. Implement persistence layer.
-      throw new RuntimeException("JBossManager.load(): Method not implemented.");
-   }
-
-   public void backgroundProcess()
-   {
-      // Called from Catalina StandardEngine for every 60 seconds.
-      
-      synchronized (backgroundProcessAllowed)
+      Engine e = null;
+      for (Container c = getContainer(); e == null && c != null; c = c.getParent())
       {
-         if (backgroundProcessAllowed.get())
+         if (c != null && c instanceof Engine)
          {
-            long start = System.currentTimeMillis();
-            
-            processExpires();
-            
-            long elapsed = System.currentTimeMillis() - start;
-            
-            processingTime_ += elapsed;
+            e = (Engine) c;
          }
       }
+      return e;
    }
 
-   /**
-    * Go through all sessions and look if they have expired
-    */
-   protected abstract void processExpires();
-
-   public void propertyChange(PropertyChangeEvent evt)
-   {
-      // TODO Need to handle it here.
-   }
-
-   /**
-    * Find in-memory sessions, if any.
-    * @return local session found. Sessions of size 0, if not found.
-    */
-   abstract public ClusteredSession[] findLocalSessions();
-
-   /**
-    * Find in-memory sessions, if any.
-    * @param realId the Session id without JvmRoute tag.
-    * @return local session found. Null if not found.
-    */
-   abstract public ClusteredSession findLocalSession(String realId);
-
 }

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossManagerMBean.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossManagerMBean.java	2008-08-25 15:10:56 UTC (rev 77438)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/JBossManagerMBean.java	2008-08-25 15:16:30 UTC (rev 77439)
@@ -47,16 +47,23 @@
    void resetStats();
 
    /**
-    * Gets the elapsed time since this manager was instantiated or the 
-    * last call to resetStats()
+    * Gets the elapsed time (in seconds) since this manager was instantiated 
+    * or the last call to resetStats()
     */
-   long timeInSecondsSinceLastReset();
+   long getTimeSinceLastReset();
 
    /**
     * Gets the number of sessions active on this node.  This includes
     * replicated sessions that have not been accessed on this node.
     */
    long getActiveSessionCount();
+   
+   /**
+    * Gets the count of sessions known to this manager, excluding those
+    * in the distributed stored that have not been accessed on this node.
+    */
+   long getLocalActiveSessionCount();
+   
 
    /**
     * Gets the number of times session creation has failed because the
@@ -85,6 +92,11 @@
    long getMaxActiveSessionCount();
    
    /**
+    * Gets the highest value seen for {@link #getLocalSessionCount()}
+    */
+   long getMaxLocalActiveSessionCount();
+   
+   /**
     * Gets the maximum number of {@link #getActiveSessionCount() active sessions}
     * that will concurrently be allowed on this node.  This includes replicated 
     * sessions that have not been accessed on this node.
@@ -154,4 +166,79 @@
     * any jvmRoute.
     */
    int getSessionIdLength();
+   
+   // StandardManager Attributes
+   
+   /**
+    * Gets the message digest algorithm to be used when generating
+    * session identifiers.
+    */
+   String getAlgorithm();
+   
+   /**
+    * Gets the fully qualified class name of the managed object
+    */
+   String getClassName();
+   
+   /**
+    * Gets the maximum number of active Sessions allowed, or -1
+      for no limit.
+    */
+   int getMaxActiveSessions();
+   
+   /**
+    * Gets the frequency of the manager checks (expiration and passivation)
+    */
+   int getProcessExpiresFrequency();
+   
+   /**
+    * Sets the frequency of the manager checks (expiration and passivation)
+    */
+   void setProcessExpiresFrequency(int frequency);
+   
+   /**
+    * Gets the name of this Manager implementation.
+    */
+   String getName();
+   
+   /**
+    * Number of active sessions at this moment. 
+    * Same as {@link #getActiveSessionCount()}.
+    */
+   int getActiveSessions();
+   
+   /**
+    * Total number of sessions created by this manager. 
+    * Same as {@link #getCreatedSessionCount()}
+    */
+   int getSessionCounter();
+   
+   /**
+    * Gets the maximum number of active sessions so far. 
+    * Same as {@link #getMaxActiveSessionCount()}
+    */
+   int getMaxActive();
+   
+   /**
+    * Gets the longest time an expired session had been alive
+    */
+   int getSessionMaxAliveTime();
+   
+   /**
+    * Gets the average time an expired session had been alive
+    */
+   int getSessionAverageAliveTime();
+   
+   /**
+    * Gets the number of sessions that expired. 
+    * Same as {@link #getExpiredSessionCount()}
+    */
+   int getExpiredSessions();
+   
+   /**
+    * Gets the number of sessions we rejected due to maxActive being reached. 
+    * Same as {@link #getRejectedSessionCount()}
+    */
+   int getRejectedSessions();
+   
 }




More information about the jboss-cvs-commits mailing list