[jboss-cvs] JBossAS SVN: r77673 - in projects/cluster/ha-server-cache-jbc/trunk: .settings and 17 other directories.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Fri Aug 29 22:55:20 EDT 2008


Author: bstansberry at jboss.com
Date: 2008-08-29 22:55:19 -0400 (Fri, 29 Aug 2008)
New Revision: 77673

Added:
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManager.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManagerMBean.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManagerManagedCache.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/DependencyInjectedConfigurationRegistry.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/PojoCacheManagerManagedPojoCache.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/framework/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/framework/server/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/framework/server/DistributedStateImpl.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/framework/server/DistributedStateImplMBean.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/jndi/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/jndi/impl/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/jndi/impl/jbc/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/jndi/impl/jbc/JBossCacheDistributedTreeManager.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/jndi/impl/jbc/JBossCacheDistributedTreeManagerMBean.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/DistributedCacheManagerFactoryImpl.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/BatchingManagerImpl.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/CacheListener.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/CacheListenerBase.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/FieldBasedJBossCacheService.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/JBossCacheService.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/JBossCacheWrapper.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/PassivationListener.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/Util.java
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/sso/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/sso/jbc/
   projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/sso/jbc/JBossCacheSSOClusterManager.java
Modified:
   projects/cluster/ha-server-cache-jbc/trunk/
   projects/cluster/ha-server-cache-jbc/trunk/.classpath
   projects/cluster/ha-server-cache-jbc/trunk/.project
   projects/cluster/ha-server-cache-jbc/trunk/.settings/org.eclipse.jdt.core.prefs
   projects/cluster/ha-server-cache-jbc/trunk/pom.xml
Log:
[JBCLUSTER-211] Remove the JBC integration SPIs and impls from the AS code base


Property changes on: projects/cluster/ha-server-cache-jbc/trunk
___________________________________________________________________
Name: svn:ignore
   + target


Modified: projects/cluster/ha-server-cache-jbc/trunk/.classpath
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/.classpath	2008-08-30 02:46:26 UTC (rev 77672)
+++ projects/cluster/ha-server-cache-jbc/trunk/.classpath	2008-08-30 02:55:19 UTC (rev 77673)
@@ -9,6 +9,7 @@
   <classpathentry kind="var" path="M2_REPO/org/jboss/jboss-common-core/2.2.7.GA/jboss-common-core-2.2.7.GA.jar" sourcepath="M2_REPO/org/jboss/jboss-common-core/2.2.7.GA/jboss-common-core-2.2.7.GA-sources.jar"/>
   <classpathentry kind="var" path="M2_REPO/org/jboss/cluster/jboss-ha-client/1.1.0.CR4/jboss-ha-client-1.1.0.CR4.jar" sourcepath="M2_REPO/org/jboss/cluster/jboss-ha-client/1.1.0.CR4/jboss-ha-client-1.1.0.CR4-sources.jar"/>
   <classpathentry kind="var" path="M2_REPO/org/jboss/cluster/jboss-ha-server-api/1.1.0.CR4/jboss-ha-server-api-1.1.0.CR4.jar" sourcepath="M2_REPO/org/jboss/cluster/jboss-ha-server-api/1.1.0.CR4/jboss-ha-server-api-1.1.0.CR4-sources.jar"/>
+  <classpathentry kind="var" path="M2_REPO/org/jboss/cluster/jboss-ha-server-cache-spi/1.0.0-SNAPSHOT/jboss-ha-server-cache-spi-1.0.0-SNAPSHOT.jar" sourcepath="M2_REPO/org/jboss/cluster/jboss-ha-server-cache-spi/1.0.0-SNAPSHOT/jboss-ha-server-cache-spi-1.0.0-SNAPSHOT-sources.jar"/>
   <classpathentry kind="var" path="M2_REPO/org/jboss/logging/jboss-logging-spi/2.0.5.GA/jboss-logging-spi-2.0.5.GA.jar" sourcepath="M2_REPO/org/jboss/logging/jboss-logging-spi/2.0.5.GA/jboss-logging-spi-2.0.5.GA-sources.jar"/>
   <classpathentry kind="var" path="M2_REPO/org/jboss/jboss-mdr/2.0.0.Beta15/jboss-mdr-2.0.0.Beta15.jar" sourcepath="M2_REPO/org/jboss/jboss-mdr/2.0.0.Beta15/jboss-mdr-2.0.0.Beta15-sources.jar"/>
   <classpathentry kind="var" path="M2_REPO/org/jboss/jboss-reflect/2.0.0.Beta12/jboss-reflect-2.0.0.Beta12.jar" sourcepath="M2_REPO/org/jboss/jboss-reflect/2.0.0.Beta12/jboss-reflect-2.0.0.Beta12-sources.jar"/>
@@ -18,5 +19,7 @@
   <classpathentry kind="var" path="M2_REPO/org/jboss/cache/jbosscache-core/2.2.0.GA/jbosscache-core-2.2.0.GA.jar"/>
   <classpathentry kind="var" path="M2_REPO/org/jboss/cache/jbosscache-pojo/2.2.0.CR5/jbosscache-pojo-2.2.0.CR5.jar"/>
   <classpathentry kind="var" path="M2_REPO/jgroups/jgroups/2.6.3.GA/jgroups-2.6.3.GA.jar"/>
+  <classpathentry kind="var" path="M2_REPO/org/jboss/naming/jnp-client/5.0.0.CR2/jnp-client-5.0.0.CR2.jar" sourcepath="M2_REPO/org/jboss/naming/jnp-client/5.0.0.CR2/jnp-client-5.0.0.CR2-sources.jar"/>
+  <classpathentry kind="var" path="M2_REPO/javax/transaction/jta/1.1/jta-1.1.jar"/>
   <classpathentry kind="var" path="M2_REPO/junit/junit/3.8.1/junit-3.8.1.jar" sourcepath="M2_REPO/junit/junit/3.8.1/junit-3.8.1-sources.jar"/>
 </classpath>
\ No newline at end of file

Modified: projects/cluster/ha-server-cache-jbc/trunk/.project
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/.project	2008-08-30 02:46:26 UTC (rev 77672)
+++ projects/cluster/ha-server-cache-jbc/trunk/.project	2008-08-30 02:55:19 UTC (rev 77673)
@@ -1,17 +1,13 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <projectDescription>
-	<name>jboss-ha-server-cache-jbc</name>
-	<comment>A JBoss Cache-based implementation of the SPIs defined in ha-server-cache-spi</comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
+  <name>jboss-ha-server-cache-jbc</name>
+  <comment>A JBoss Cache-based implementation of the SPIs defined in ha-server-cache-spi</comment>
+  <projects/>
+  <buildSpec>
+    <buildCommand>
+      <name>org.eclipse.jdt.core.javabuilder</name>
+    </buildCommand>
+  </buildSpec>
+  <natures>
+    <nature>org.eclipse.jdt.core.javanature</nature>
+  </natures>
+</projectDescription>
\ No newline at end of file

Modified: projects/cluster/ha-server-cache-jbc/trunk/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/.settings/org.eclipse.jdt.core.prefs	2008-08-30 02:46:26 UTC (rev 77672)
+++ projects/cluster/ha-server-cache-jbc/trunk/.settings/org.eclipse.jdt.core.prefs	2008-08-30 02:55:19 UTC (rev 77673)
@@ -1,4 +1,4 @@
-#Fri Aug 29 17:08:02 CDT 2008
+#Fri Aug 29 20:33:26 CDT 2008
 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
 eclipse.preferences.version=1
 org.eclipse.jdt.core.compiler.source=1.5

Modified: projects/cluster/ha-server-cache-jbc/trunk/pom.xml
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/pom.xml	2008-08-30 02:46:26 UTC (rev 77672)
+++ projects/cluster/ha-server-cache-jbc/trunk/pom.xml	2008-08-30 02:55:19 UTC (rev 77673)
@@ -28,15 +28,15 @@
   </scm>
   
   <properties>
-    <version.jboss.ha.server.api>1.1.0.CR4</version.jboss.ha.server.api>
     <version.jboss.ha.server.cache.spi>1.0.0-SNAPSHOT</version.jboss.ha.server.cache.spi>
-    <version.jboss.common.core>2.2.7.GA</version.jboss.common.core>
+    <version.jboss.ha.server.api>1.1.0-SNAPSHOT</version.jboss.ha.server.api>
+    <version.jboss.common.core>2.2.8.GA</version.jboss.common.core>
     <version.jboss.logging.spi>2.0.5.GA</version.jboss.logging.spi>
     <version.jboss.cache>2.2.0.GA</version.jboss.cache>
     <!-- This should be [2.6.2,3.0.0) but maven complains about it -->
     <version.jgroups>2.6.3.GA</version.jgroups>
     <version.jboss.cache.pojo>2.2.0.CR5</version.jboss.cache.pojo>
-    <version.jboss.aop>2.0.0.CR16</version.jboss.aop>
+    <version.org.jboss.naming>5.0.0.CR2</version.org.jboss.naming>
     <version.junit>3.8.1</version.junit>
   </properties>
   
@@ -61,11 +61,19 @@
       <artifactId>jboss-ha-server-api</artifactId>
       <version>${version.jboss.ha.server.api}</version>
     </dependency>
+    
     <dependency>
+      <groupId>org.jboss.cluster</groupId>
+      <artifactId>jboss-ha-server-cache-spi</artifactId>
+      <version>${version.jboss.ha.server.cache.spi}</version>
+    </dependency>
+    
+    <dependency>
       <groupId>org.jboss</groupId>
       <artifactId>jboss-common-core</artifactId>
       <version>${version.jboss.common.core}</version>
     </dependency>
+    
     <dependency>
       <groupId>org.jboss.logging</groupId>
       <artifactId>jboss-logging-spi</artifactId>
@@ -83,10 +91,6 @@
           <artifactId>jgroups</artifactId>
         </exclusion>
         <exclusion>
-          <groupId>javax.transaction</groupId>
-          <artifactId>jta</artifactId>
-        </exclusion>
-        <exclusion>
           <groupId>org.jboss</groupId>
           <artifactId>jboss-common-core</artifactId>
         </exclusion>
@@ -117,6 +121,7 @@
         </exclusion>
       </exclusions>
     </dependency>
+    
     <dependency>
       <groupId>jgroups</groupId>
       <artifactId>jgroups</artifactId>
@@ -129,56 +134,32 @@
         </exclusion>
       </exclusions>
     </dependency>
+
+    <!-- For the JTA 1.1 API; consuming projects can safely
+         exclude this and replace with any valid source of this API -->
+    <dependency>
+       <groupId>javax.transaction</groupId>
+       <artifactId>jta</artifactId>
+       <version>1.1</version>
+    </dependency>
     
     <dependency>
-      <groupId>org.jboss.aop</groupId>
-      <artifactId>jboss-aop</artifactId>
-      <version>${version.jboss.aop}</version>
-      <optional>true</optional>
-      <exclusions>
-        <!--  Don't pull in all the stuff from aop -->
-        <exclusion>
-          <groupId>ant</groupId>
-          <artifactId>ant</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>apache-xerces</groupId>
-          <artifactId>xml-apis</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>org.jboss</groupId>
-          <artifactId>javassist</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>org.jboss.microcontainer</groupId>
-          <artifactId>jboss-container</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>jboss</groupId>
-          <artifactId>jboss-common-logging-spi</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>jboss</groupId>
-          <artifactId>jboss-common-logging-log4j</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>qdox</groupId>
-          <artifactId>qdox</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>trove</groupId>
-          <artifactId>trove</artifactId>
-        </exclusion>
-      </exclusions>
+      <groupId>org.jboss.naming</groupId>
+      <artifactId>jnp-client</artifactId>
+      <version>${version.org.jboss.naming}</version>
     </dependency>
     
     <!-- Test dependencies -->
+    
+    <!-- 
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>${version.junit}</version>
       <scope>test</scope>
     </dependency>
+    -->
+    
   </dependencies>  
 
 </project>

Copied: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManager.java (from rev 77668, trunk/cluster/src/main/org/jboss/ha/cachemanager/CacheManager.java)
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManager.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManager.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,733 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.ha.cachemanager;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.JMException;
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import javax.naming.Reference;
+import javax.naming.StringRefAddr;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.CacheStatus;
+import org.jboss.cache.config.Configuration;
+import org.jboss.cache.config.ConfigurationRegistry;
+import org.jboss.cache.jmx.CacheJmxWrapper;
+import org.jboss.cache.notifications.annotation.CacheListener;
+import org.jboss.cache.notifications.annotation.CacheStarted;
+import org.jboss.cache.notifications.annotation.CacheStopped;
+import org.jboss.cache.notifications.event.CacheStartedEvent;
+import org.jboss.cache.notifications.event.CacheStoppedEvent;
+import org.jboss.cache.pojo.PojoCache;
+import org.jboss.cache.pojo.PojoCacheFactory;
+import org.jboss.cache.pojo.jmx.PojoCacheJmxWrapper;
+import org.jboss.ha.framework.server.CacheManagerLocator;
+import org.jboss.ha.framework.server.PojoCacheManager;
+import org.jboss.ha.framework.server.PojoCacheManagerLocator;
+import org.jboss.logging.Logger;
+import org.jboss.util.naming.NonSerializableFactory;
+import org.jgroups.ChannelFactory;
+
+/**
+ * JBoss AS specific {@link CacheManager}. Extends the core JBoss Cache
+ * cache manager by also handling, PojoCache, by registering created caches
+ * in JMX, and by registering itself in JNDI.
+ * 
+ * @author <a href="brian.stansberry at jboss.com">Brian Stansberry</a>
+ * @version $Revision: 1.1 $
+ */
+public class CacheManager 
+   extends org.jboss.cache.CacheManagerImpl
+   implements org.jboss.cache.CacheManager, PojoCacheManager, MBeanRegistration, CacheManagerMBean
+{
+   private static final Logger log = Logger.getLogger(CacheManager.class);
+   
+   public static final String DEFAULT_CORE_CACHE_JMX_ATTRIBUTES = "service=Cache,config=";
+   public static final String DEFAULT_POJO_CACHE_JMX_ATTRIBUTES = "service=Cache,cacheType=PojoCache,config=";
+   
+   private MBeanServer mbeanServer;
+   private String jmxDomain;
+   private String coreCacheJmxAttributes = DEFAULT_CORE_CACHE_JMX_ATTRIBUTES;
+   private String pojoCacheJmxAttributes = DEFAULT_POJO_CACHE_JMX_ATTRIBUTES;
+
+   private Map<String, PojoCache> pojoCaches = new HashMap<String, PojoCache>();
+
+   private Map<String, Integer> pojoCacheCheckouts = new HashMap<String, Integer>();
+
+   private Map<String, String> configAliases = new HashMap<String, String>();
+   
+   private boolean registerCachesInJmx = true;
+   
+   private Map<String, Boolean> startupCaches = new HashMap<String, Boolean>();
+   
+   private String jndiName;
+   private boolean started;
+   
+   /**
+    * Create a new CacheManagerImpl.
+    * 
+    */
+   public CacheManager()
+   {
+      super();
+   }
+
+   /**
+    * Create a new CacheManagerImpl.
+    * 
+    * @param configRegistry
+    * @param factory
+    */
+   public CacheManager(ConfigurationRegistry configRegistry, ChannelFactory factory)
+   {
+      super(configRegistry, factory);
+   }
+
+   /**
+    * Create a new CacheManagerImpl.
+    * 
+    * @param configFileName
+    * @param factory
+    */
+   public CacheManager(String configFileName, ChannelFactory factory)
+   {
+      super(configFileName, factory);
+   }
+   
+   // -------------------------------------------------------- PojoCacheManager
+
+   @SuppressWarnings("unchecked")
+   public Set<String> getConfigurationNames()
+   {
+      synchronized (pojoCaches)
+      {
+         Set<String> configNames = super.getConfigurationNames();
+         configNames.addAll(getPojoCacheNames());
+         configNames.addAll(configAliases.keySet());
+         return configNames;
+      }
+   }
+
+   public Set<String> getPojoCacheNames()
+   {
+      synchronized (pojoCaches)
+      {
+         return new HashSet<String>(pojoCaches.keySet());
+      }
+   }
+
+   public PojoCache getPojoCache(String configName, boolean create) throws Exception
+   {
+      // Check if there's an alias involved
+      configName = resolveAlias(configName);
+      
+      PojoCache cache = null;
+      synchronized (pojoCaches)
+      {
+         if (getCacheNames().contains(configName))
+            throw new IllegalStateException("Cannot create PojoCache: plain cache already created for config " + configName);
+         
+         cache = pojoCaches.get(configName);
+         if (cache == null && create)
+         {
+            Configuration config = getConfigurationRegistry().getConfiguration(configName);
+            if (getChannelFactory() != null && config.getMultiplexerStack() != null)
+            {
+               config.getRuntimeConfig().setMuxChannelFactory(getChannelFactory());
+            }
+            cache = createPojoCache(config);
+            registerPojoCache(cache, configName);
+         }
+         else if (cache != null)
+         {
+            incrementPojoCacheCheckout(configName);
+         }
+      }
+
+      // Wrap the pojocache to control classloading and disable stop/destroy
+      return cache == null ? null : new PojoCacheManagerManagedPojoCache(cache);
+   }
+   
+   /**
+    * Extension point for subclasses, where we actually use a
+    * {@link PojoCacheFactory} to create a PojoCache.
+    * 
+    * @param config the Configuration for the cache
+    * @return the PojoCache
+    */
+   @SuppressWarnings("unchecked")
+   protected PojoCache createPojoCache(Configuration config)
+   {
+       return PojoCacheFactory.createCache(config, false);
+   }
+
+   public void registerPojoCache(PojoCache cache, String configName)
+   {
+      synchronized (pojoCaches)
+      {
+         if (pojoCaches.containsKey(configName) || getCacheNames().contains(configName))
+            throw new IllegalStateException(configName + " already registered");
+         
+         pojoCaches.put(configName, cache);
+         incrementPojoCacheCheckout(configName);
+         
+         if (registerCachesInJmx && mbeanServer != null)
+         {
+            String oName = getObjectName(getPojoCacheJmxAttributes(), configName);
+            PojoCacheJmxWrapper wrapper = new PojoCacheJmxWrapper(cache);
+            try
+            {
+               mbeanServer.registerMBean(wrapper, new ObjectName(oName));
+            }
+            catch (JMException e)
+            {
+               throw new RuntimeException("Cannot register cache under name " + oName, e);
+            }
+            
+            // Synchronize the start/stop of the plain and pojo cache
+            cache.getCache().addCacheListener(new StartStopListener(wrapper));
+         }
+      }
+   }
+   
+   // -------------------------------------------------------------  Overrides
+   
+   @Override
+   public Cache<Object, Object> getCache(String configName, boolean create) throws Exception
+   {
+      // Check if there's an alias involved
+      configName = resolveAlias(configName);
+      
+      synchronized (pojoCaches)
+      {
+         if (create && pojoCaches.containsKey(configName))
+         {
+            log.debug("Plain cache requested for config " + configName + 
+                      " but a PojoCache is already registered; returning " +
+                      " the PojoCache's underlying plain cache");
+            PojoCache pc = getPojoCache(configName, false);
+            if (pc != null)
+               return wrapCache(pc.getCache());
+         }
+         
+         return wrapCache(super.getCache(configName, create));
+      }
+   }
+    
+   @Override
+   public void registerCache(Cache<Object, Object> cache, String configName)
+   {
+      synchronized (pojoCaches)
+      {
+         if (pojoCaches.containsKey(configName))
+            throw new IllegalStateException(configName + " already registered");
+         
+         super.registerCache(cache, configName);
+         
+         if (registerCachesInJmx && mbeanServer != null)
+         {
+            String oName = getObjectName(getCoreCacheJmxAttributes(), configName);
+            CacheJmxWrapper wrapper = new CacheJmxWrapper(cache);
+            try
+            {
+               mbeanServer.registerMBean(wrapper, new ObjectName(oName));
+            }
+            catch (JMException e)
+            {
+               throw new RuntimeException("Cannot register cache under name " + oName, e);
+            }
+            
+            // Synchronize the start/stop of the plain and pojo cache
+            cache.addCacheListener(new StartStopListener(wrapper));
+         }
+      }
+   }
+ 
+   @Override
+   public void releaseCache(String configName)
+   {
+      // Check if there's an alias involved
+      configName = resolveAlias(configName);  
+      
+      synchronized (pojoCaches)
+      {
+         if (pojoCaches.containsKey(configName))
+         {
+            if (decrementPojoCacheCheckout(configName) == 0)
+            {
+               PojoCache cache = pojoCaches.remove(configName);
+               destroyPojoCache(configName, cache);
+            }            
+         }
+         else
+         {
+            super.releaseCache(configName);
+            
+            if (registerCachesInJmx && mbeanServer != null && !getCacheNames().contains(configName))
+            {
+               String oNameStr = getObjectName(getCoreCacheJmxAttributes(), configName);
+               try
+               {
+                  ObjectName oName = new ObjectName(oNameStr);
+                  if (mbeanServer.isRegistered(oName))
+                  {
+                     mbeanServer.unregisterMBean(oName);
+                  }
+               }
+               catch (JMException e)
+               {
+                  log.error("Problem unregistering PojoCacheJmxWrapper " + oNameStr, e);
+               }
+               
+            }
+         }
+      }
+   }
+
+   @Override
+   public void start() throws Exception
+   {
+      if (!started)
+      {
+         super.start();
+         
+         startEagerStartCaches();
+         
+         CacheManagerLocator locator = CacheManagerLocator.getCacheManagerLocator();
+         if (locator.getDirectlyRegisteredManager() == null)
+            locator.registerCacheManager(this);
+         
+         PojoCacheManagerLocator pclocator = PojoCacheManagerLocator.getCacheManagerLocator();
+         if (pclocator.getDirectlyRegisteredManager() == null)
+            pclocator.registerCacheManager(this);
+         
+         // Bind ourself in the public JNDI space if configured to do so
+         if (jndiName != null)
+         {
+            Context ctx = new InitialContext();
+            this.bind(jndiName, this, CacheManager.class, ctx);
+            log.debug("Bound in JNDI under " + jndiName);
+         }
+         
+         started = true;
+      }
+   }
+
+   @Override
+   public void stop()
+   {
+      if (started)
+      {
+         releaseEagerStartCaches();
+         
+         synchronized (pojoCaches)
+         {
+            for (Iterator<Map.Entry<String, PojoCache>> it = pojoCaches.entrySet().iterator(); it.hasNext();)
+            {
+               Map.Entry<String, PojoCache> entry = it.next();
+               destroyPojoCache(entry.getKey(), entry.getValue());
+               it.remove();
+            }
+            pojoCaches.clear();
+            pojoCacheCheckouts.clear();
+         }
+         
+         super.stop();
+         
+         if (jndiName != null)
+         {
+            InitialContext ctx = null;
+            try
+            {
+               // the following statement fails when the server is being shut down (07/19/2007)
+               ctx = new InitialContext();
+               ctx.unbind(jndiName);
+            }
+            catch (Exception e) {
+               log.error("partition unbind operation failed", e);
+            }
+            finally
+            {
+               if (ctx != null)
+               {
+                  try
+                  {
+                     ctx.close();
+                  }
+                  catch (NamingException e)
+                  {
+                     log.error("Caught exception closing naming context", e);
+                  }
+               }
+            }
+            
+            try
+            {
+               NonSerializableFactory.unbind (jndiName);
+            }
+            catch (NameNotFoundException e)
+            {
+               log.error("Caught exception unbinding from NonSerializableFactory", e);
+            }         
+         }
+
+         CacheManagerLocator locator = CacheManagerLocator.getCacheManagerLocator();
+         if (locator.getDirectlyRegisteredManager() == this)
+            locator.deregisterCacheManager();
+
+         PojoCacheManagerLocator pclocator = PojoCacheManagerLocator.getCacheManagerLocator();
+         if (pclocator.getDirectlyRegisteredManager() == this)
+            pclocator.deregisterCacheManager();
+         
+         started = false;
+      }
+   }
+   
+   
+   
+   // -------------------------------------------------------------  Properties
+
+   public String getJmxDomain()
+   {
+      return jmxDomain;
+   }
+
+   public void setJmxDomain(String jmxDomain)
+   {
+      this.jmxDomain = jmxDomain;
+   }
+
+   public String getCoreCacheJmxAttributes()
+   {
+      return coreCacheJmxAttributes;
+   }
+
+   public void setCoreCacheJmxAttributes(String coreCacheJmxAttributes)
+   {
+      this.coreCacheJmxAttributes = coreCacheJmxAttributes;
+   }
+
+   public String getPojoCacheJmxAttributes()
+   {
+      return pojoCacheJmxAttributes;
+   }
+
+   public void setPojoCacheJmxAttributes(String pojoCacheJmxAttributes)
+   {
+      this.pojoCacheJmxAttributes = pojoCacheJmxAttributes;
+   }
+   
+   public boolean getRegisterCachesInJmx()
+   {
+      return registerCachesInJmx;
+   }
+
+   public void setRegisterCachesInJmx(boolean register)
+   {
+      this.registerCachesInJmx = register;
+   }
+
+   public String getJndiName()
+   {
+      return jndiName;
+   }
+
+   public void setJndiName(String jndiName)
+   {
+      this.jndiName = jndiName;
+   }
+   
+   
+   public Map<String, String> getConfigAliases()
+   {
+      synchronized (configAliases)
+      {
+         return new HashMap<String, String>(configAliases);
+      }
+   }
+   
+   public void setConfigAliases(Map<String, String> aliases)
+   {
+      synchronized (configAliases)
+      {
+         configAliases.clear();
+         if (aliases != null)
+            configAliases.putAll(aliases);
+      }
+   }
+   
+   public void setEagerStartCaches(Set<String> configNames)
+   {
+      if (configNames != null)
+      {
+         for (String name : configNames)
+         {
+            startupCaches.put(name, Boolean.FALSE);
+         }
+      }
+   }
+   
+   public void setEagerStartPojoCaches(Set<String> configNames)
+   {
+      if (configNames != null)
+      {
+         for (String name : configNames)
+         {
+            startupCaches.put(name, Boolean.TRUE);
+         }
+      }
+   }
+   
+   
+   
+   // ------------------------------------------------------  MBeanRegistration
+
+   public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception
+   {
+      this.mbeanServer = server;
+      if (jmxDomain == null)
+      {
+         jmxDomain = name.getDomain();
+      }
+      return name;
+   }
+
+   public void postDeregister()
+   {
+      // no-op      
+   }
+
+   public void preDeregister() throws Exception
+   {
+      // TODO Auto-generated method stub
+      
+   }
+
+   public void postRegister(Boolean registrationDone)
+   {
+      // no-op
+   }
+
+   // ----------------------------------------------------------------  Private
+   
+   /**
+    * Wrap the pojocache to control classloading and disable stop/destroy
+    */
+   private Cache wrapCache(Cache toWrap)
+   {
+      return toWrap == null ? null : new CacheManagerManagedCache(toWrap);
+   }
+
+   private int incrementPojoCacheCheckout(String configName)
+   {
+      synchronized (pojoCacheCheckouts)
+      {
+         Integer count = pojoCacheCheckouts.get(configName);
+         if (count == null)
+            count = new Integer(0);
+         Integer newVal = new Integer(count.intValue() + 1);
+         pojoCacheCheckouts.put(configName, newVal);
+         return newVal.intValue();
+      }
+   }
+
+   private int decrementPojoCacheCheckout(String configName)
+   {
+      synchronized (pojoCacheCheckouts)
+      {
+         Integer count = pojoCacheCheckouts.get(configName);
+         if (count == null || count.intValue() < 1)
+            throw new IllegalStateException("invalid count of " + count + " for " + configName);
+
+         Integer newVal = new Integer(count.intValue() - 1);
+         pojoCacheCheckouts.put(configName, newVal);
+         return newVal.intValue();
+      }
+   }
+
+   private void destroyPojoCache(String configName, PojoCache pojoCache)
+   {
+      Cache<Object, Object> cache = pojoCache.getCache();
+      if (cache.getCacheStatus() == CacheStatus.STARTED)
+      {
+         pojoCache.stop();
+      }
+      if (cache.getCacheStatus() != CacheStatus.DESTROYED && cache.getCacheStatus() != CacheStatus.INSTANTIATED)
+      {
+         pojoCache.destroy();
+      }
+      
+      if (registerCachesInJmx && mbeanServer != null)
+      {
+         String oNameStr = getObjectName(getPojoCacheJmxAttributes(), configName);
+         try
+         {
+            ObjectName oName = new ObjectName(oNameStr);
+            if (mbeanServer.isRegistered(oName))
+            {
+               mbeanServer.unregisterMBean(oName);
+            }
+         }
+         catch (JMException e)
+         {
+            log.error("Problem unregistering PojoCacheJmxWrapper " + oNameStr, e);
+         }
+      }
+   }
+   
+   private String getObjectName(String attributesBase, String configName)
+   {
+      String base = getJmxDomain() == null ? "" : getJmxDomain();
+      return base + ":" + attributesBase + configName;
+   }
+   
+   @CacheListener
+   public static class StartStopListener
+   {
+      private final CacheJmxWrapper plainWrapper;
+      private final PojoCacheJmxWrapper pojoWrapper;
+      
+      private StartStopListener(CacheJmxWrapper wrapper)
+      {
+         assert wrapper != null : "wrapper is null";
+         this.plainWrapper = wrapper;
+         this.pojoWrapper = null;
+      }
+      
+      private StartStopListener(PojoCacheJmxWrapper wrapper)
+      {
+         assert wrapper != null : "wrapper is null";
+         this.pojoWrapper = wrapper;
+         this.plainWrapper = null;
+      }
+      
+      @CacheStarted
+      public void cacheStarted(CacheStartedEvent event)
+      {
+         if (plainWrapper != null)
+            plainWrapper.start();
+         else
+            pojoWrapper.start();
+      }
+      
+      @CacheStopped
+      public void cacheStopped(CacheStoppedEvent event)
+      {
+         if (plainWrapper != null)
+            plainWrapper.stop();
+         else
+            pojoWrapper.stop();
+         
+      }
+   }
+
+   /**
+    * Helper method that binds the partition in the JNDI tree.
+    * @param jndiName Name under which the object must be bound
+    * @param who Object to bind in JNDI
+    * @param classType Class type under which should appear the bound object
+    * @param ctx Naming context under which we bind the object
+    * @throws Exception Thrown if a naming exception occurs during binding
+    */   
+   private void bind(String jndiName, Object who, Class classType, Context ctx) throws Exception
+   {
+      // Ah ! This service isn't serializable, so we use a helper class
+      //
+      NonSerializableFactory.bind(jndiName, who);
+      Name n = ctx.getNameParser("").parse(jndiName);
+      while (n.size () > 1)
+      {
+         String ctxName = n.get (0);
+         try
+         {
+            ctx = (Context)ctx.lookup (ctxName);
+         }
+         catch (NameNotFoundException e)
+         {
+            log.debug ("creating Subcontext " + ctxName);
+            ctx = ctx.createSubcontext (ctxName);
+         }
+         n = n.getSuffix (1);
+      }
+
+      // The helper class NonSerializableFactory uses address type nns, we go on to
+      // use the helper class to bind the service object in JNDI
+      //
+      StringRefAddr addr = new StringRefAddr("nns", jndiName);
+      Reference ref = new Reference(classType.getName (), addr, NonSerializableFactory.class.getName (), null);
+      ctx.rebind (n.get (0), ref);
+   }
+   
+   private String resolveAlias(String configName)
+   {
+      String alias = configAliases.get(configName);
+      return alias == null ? configName : alias;
+   }
+   
+   private void startEagerStartCaches() throws Exception
+   {
+      for (Map.Entry<String, Boolean> entry : startupCaches.entrySet())
+      {
+         Cache cache = null;
+         if (entry.getValue().booleanValue())
+         {
+            PojoCache pc = getPojoCache(entry.getKey(), true);
+            cache = pc.getCache();
+         }
+         else
+         {
+            cache = getCache(entry.getKey(), true);
+         }
+         
+         if (cache.getCacheStatus() != CacheStatus.STARTED)
+         {
+            if (cache.getCacheStatus() != CacheStatus.CREATED)
+            {
+               cache.create();
+            }
+            cache.start();
+         }
+      }
+   }
+   
+   private void releaseEagerStartCaches()
+   {
+      for (String name : startupCaches.keySet())
+      {
+         releaseCache(name);
+      }
+   }
+
+}

Copied: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManagerMBean.java (from rev 77655, trunk/cluster/src/main/org/jboss/ha/cachemanager/CacheManagerMBean.java)
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManagerMBean.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManagerMBean.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.ha.cachemanager;
+
+import java.util.Set;
+
+/**
+ * A CacheManagerMBean.
+ * 
+ * @author <a href="brian.stansberry at jboss.com">Brian Stansberry</a>
+ * @version $Revision: 1.1 $
+ */
+public interface CacheManagerMBean
+{
+   String getJmxDomain();
+   String getCoreCacheJmxAttributes();
+   String getPojoCacheJmxAttributes();
+   Set<String> getPojoCacheNames();
+   Set<String> getCacheNames();
+   Set<String> getConfigurationNames();
+   boolean getRegisterCachesInJmx();
+   String getJndiName();
+}

Copied: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManagerManagedCache.java (from rev 77668, trunk/cluster/src/main/org/jboss/ha/cachemanager/CacheManagerManagedCache.java)
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManagerManagedCache.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/CacheManagerManagedCache.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,322 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.ha.cachemanager;
+
+import java.security.AccessController;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.CacheException;
+import org.jboss.cache.CacheStatus;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.InvocationContext;
+import org.jboss.cache.Node;
+import org.jboss.cache.NodeNotExistsException;
+import org.jboss.cache.Region;
+import org.jboss.cache.config.Configuration;
+import org.jboss.logging.Logger;
+import org.jboss.util.loading.ContextClassLoaderSwitcher;
+import org.jgroups.Address;
+
+/**
+ * Wrapper around a cache that 1) ensures the calling thread's TCCL doesn't
+ * leak to cache threads via calls to create/start; and 2) logs WARNS about calls
+ * to stop/destroy as these should be handled by the CacheManager.
+ * 
+ * TODO disable calls to stop/destroy once testsuite cleanup code is fixed
+ * to not call those methods.
+ * 
+ * @author Brian Stansberry
+ */
+class CacheManagerManagedCache implements Cache
+{
+   private static final Logger log = Logger.getLogger(CacheManagerManagedCache.class);
+   
+   private final Cache delegate;
+   private final ContextClassLoaderSwitcher switcher;
+   
+   CacheManagerManagedCache(Cache delegate)
+   {
+      assert delegate != null : "delegate is null";
+      this.delegate = delegate;
+      this.switcher = (ContextClassLoaderSwitcher) AccessController.doPrivileged(ContextClassLoaderSwitcher.INSTANTIATOR);
+   }
+
+   /**
+    * Switches the TCCL to the delegate's classloader before calling create()
+    * on the delegate.
+    */
+   public void create() throws CacheException
+   {
+      ContextClassLoaderSwitcher.SwitchContext switchContext = switcher.getSwitchContext();
+      try
+      {
+         switchContext.setClassLoader(delegate.getClass().getClassLoader());
+         delegate.create();
+      }
+      finally
+      {
+         switchContext.reset();
+      }
+   }
+
+   /**
+    * Switches the TCCL to the delegate's classloader before calling start()
+    * on the delegate.
+    */
+   public void start() throws CacheException
+   {
+      ContextClassLoaderSwitcher.SwitchContext switchContext = switcher.getSwitchContext();
+      try
+      {
+         switchContext.setClassLoader(delegate.getClass().getClassLoader());
+         delegate.start();
+      }
+      finally
+      {
+         switchContext.reset();
+      }
+   }
+
+   /**
+    * TODO: Log a WARN and do nothing else; currently logs and then calls 
+    * through to delegate.
+    */
+   public void stop()
+   {
+      log.warn("stop() should not be directly called on caches obtained from a CacheManager -- use CacheManager.releaseCache()", 
+            new UnsupportedOperationException("stop() is not supported"));
+      delegate.stop();
+   }
+
+   /**
+    * TODO: Log a WARN and do nothing else; currently logs and then calls 
+    * through to delegate.
+    */
+   public void destroy()
+   {
+      log.warn("destroy() should not be directly called on caches obtained from a CacheManager -- use CacheManager.releaseCache()", 
+            new UnsupportedOperationException("destroy() is not supported"));
+      delegate.destroy();
+   }
+   
+   // ------------------------------------------------------------------ Cache
+   
+   public void addCacheListener(Object arg0)
+   {
+      delegate.addCacheListener(arg0);
+   }
+
+   public void clearData(String arg0)
+   {
+      delegate.clearData(arg0);
+   }
+
+   public void clearData(Fqn arg0)
+   {
+      delegate.clearData(arg0);
+   }
+
+   public void evict(Fqn arg0)
+   {
+      delegate.evict(arg0);
+   }
+
+   public void evict(Fqn arg0, boolean arg1)
+   {
+      delegate.evict(arg0, arg1);
+   }
+
+   public Object get(Fqn arg0, Object arg1)
+   {
+      return delegate.get(arg0, arg1);
+   }
+
+   public Object get(String arg0, Object arg1)
+   {
+      return delegate.get(arg0, arg1);
+   }
+
+   public Set getCacheListeners()
+   {
+      return delegate.getCacheListeners();
+   }
+
+   public CacheStatus getCacheStatus()
+   {
+      return delegate.getCacheStatus();
+   }
+
+   public Configuration getConfiguration()
+   {
+      return delegate.getConfiguration();
+   }
+
+   public Map getData(Fqn arg0)
+   {
+      return delegate.getData(arg0);
+   }
+
+   public InvocationContext getInvocationContext()
+   {
+      return delegate.getInvocationContext();
+   }
+
+   public Set getKeys(String arg0)
+   {
+      return getKeys(arg0);
+   }
+
+   public Set getKeys(Fqn arg0)
+   {
+      return getKeys(arg0);
+   }
+
+   public Address getLocalAddress()
+   {
+      return delegate.getLocalAddress();
+   }
+
+   public List getMembers()
+   {
+      return delegate.getMembers();
+   }
+
+   public Node getNode(Fqn arg0)
+   {
+      return delegate.getNode(arg0);
+   }
+
+   public Node getNode(String arg0)
+   {
+      return delegate.getNode(arg0);
+   }
+
+   public Region getRegion(Fqn arg0, boolean arg1)
+   {
+      return delegate.getRegion(arg0, arg1);
+   }
+
+   public Node getRoot()
+   {
+      return delegate.getRoot();
+   }
+
+   public String getVersion()
+   {
+      return delegate.getVersion();
+   }
+
+   public void move(Fqn arg0, Fqn arg1) throws NodeNotExistsException
+   {
+      delegate.move(arg0, arg1);
+   }
+
+   public void move(String arg0, String arg1) throws NodeNotExistsException
+   {
+      delegate.move(arg0, arg1);
+   }
+
+   public void put(Fqn arg0, Map arg1)
+   {
+      delegate.put(arg0, arg1);
+   }
+
+   public void put(String arg0, Map arg1)
+   {
+      delegate.put(arg0, arg1);
+   }
+
+   public Object put(Fqn arg0, Object arg1, Object arg2)
+   {
+      return delegate.put(arg0, arg1, arg2);
+   }
+
+   public Object put(String arg0, Object arg1, Object arg2)
+   {
+      return delegate.put(arg0, arg1, arg2);
+   }
+
+   public void putForExternalRead(Fqn arg0, Object arg1, Object arg2)
+   {
+      delegate.putForExternalRead(arg0, arg1, arg2);
+   }
+
+   public Object remove(Fqn arg0, Object arg1)
+   {
+      return delegate.remove(arg0, arg1);
+   }
+
+   public Object remove(String arg0, Object arg1)
+   {
+      return delegate.remove(arg0, arg1);
+   }
+
+   public void removeCacheListener(Object arg0)
+   {
+      delegate.removeCacheListener(arg0);
+   }
+
+   public boolean removeNode(Fqn arg0)
+   {
+       return delegate.removeNode(arg0);
+   }
+
+   public boolean removeNode(String arg0)
+   {
+      return delegate.removeNode(arg0);
+   }
+
+   public boolean removeRegion(Fqn arg0)
+   {
+      return delegate.removeRegion(arg0);
+   }
+
+   public void setInvocationContext(InvocationContext arg0)
+   {
+      delegate.setInvocationContext(arg0);
+   }
+
+   // --------------------------------------------------------------  Overrides
+
+   @Override
+   public boolean equals(Object obj)
+   {
+      if (obj instanceof CacheManagerManagedCache)
+      {
+         CacheManagerManagedCache other = (CacheManagerManagedCache) obj;
+         return delegate.equals(other.delegate);
+      }
+      return false;
+   }
+
+   @Override
+   public int hashCode()
+   {
+      return delegate.hashCode();
+   }
+   
+   
+}

Copied: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/DependencyInjectedConfigurationRegistry.java (from rev 77655, trunk/cluster/src/main/org/jboss/ha/cachemanager/DependencyInjectedConfigurationRegistry.java)
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/DependencyInjectedConfigurationRegistry.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/DependencyInjectedConfigurationRegistry.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,156 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+
+package org.jboss.ha.cachemanager;
+
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+import org.jboss.cache.config.Configuration;
+import org.jboss.cache.config.ConfigurationRegistry;
+import org.jboss.cache.factories.CacheConfigsXmlParser;
+
+/**
+ * {@link ConfigurationRegistry} that can obtain its initial set of 
+ * configurations via dependency injection.
+ * 
+ * @author <a href="brian.stansberry at jboss.com">Brian Stansberry</a>
+ * @version $Revision: 1 $
+ */
+public class DependencyInjectedConfigurationRegistry implements ConfigurationRegistry 
+{    
+    private CacheConfigsXmlParser parser;
+    private String configResource;
+    private Map<String, Configuration> configs = new Hashtable<String, Configuration>();
+    private boolean started;
+    
+    public DependencyInjectedConfigurationRegistry() 
+    {        
+        parser = new CacheConfigsXmlParser();
+    }
+    
+    public void start() throws Exception
+    {
+        if (!started) 
+        {
+            loadConfigResource(configResource);
+            started = true;
+        }
+    }
+
+   private void loadConfigResource(String resource) throws CloneNotSupportedException
+   {
+      if (resource != null)
+      {
+         Map<String, Configuration> parsed = parser.parseConfigs(resource);
+         for (Map.Entry<String, Configuration> entry : parsed.entrySet())
+         {
+            registerConfiguration(entry.getKey(), entry.getValue());
+         }
+      }
+   }
+    
+    public void stop() 
+    {
+        if (started) 
+        {
+            synchronized (configs) 
+            {
+                configs.clear();
+            }
+            started = false;
+        }
+    }
+    
+    public void setConfigResource(String resource)
+    {
+       if (started)
+       {
+          try
+          {
+             loadConfigResource(resource);
+          }
+          catch (CloneNotSupportedException e)
+          {
+             throw new RuntimeException("Configuration in " + resource + 
+                                        " does not properly support cloning", e);
+          }
+       }
+       else
+       {
+          this.configResource = resource;
+       }
+    }
+    
+    public void setNewConfigurations(Map<String, Configuration> newConfigs)
+    {
+       if (newConfigs != null)
+       {
+
+          for (Map.Entry<String, Configuration> entry : newConfigs.entrySet())
+          {
+             try
+             {
+                registerConfiguration(entry.getKey(), entry.getValue());
+             }
+             catch (CloneNotSupportedException e)
+             {
+                throw new RuntimeException("Configuration " + entry.getKey() + 
+                                           " does not properly support cloning", e);
+             }
+          }
+       }
+    }
+
+    public Set<String> getConfigurationNames()
+    {
+        return new HashSet<String>(configs.keySet());
+    }
+    
+    public void registerConfiguration(String configName, Configuration config) 
+       throws CloneNotSupportedException
+    {
+        synchronized (configs) {
+            if (configs.containsKey(configName))
+                throw new IllegalStateException(configName + " already registered");
+            configs.put(configName, config.clone());
+        }
+    }
+    
+    public void unregisterConfiguration(String configName) 
+    {
+        synchronized (configs) {
+            if (configs.remove(configName) == null)
+                throw new IllegalStateException(configName + " not registered");            
+        }
+    }
+    
+    public Configuration getConfiguration(String configName) 
+    {
+       Configuration config = null;
+       synchronized (configs)
+       {
+          config = configs.get(configName);
+       }
+       
+       if (config == null)
+          throw new IllegalArgumentException("unknown config " + configName);
+        
+       // Don't hand out a ref to our master copy 
+       try
+       {
+          return config.clone();
+       }
+       catch (CloneNotSupportedException e)
+       {
+          // This should not happen, as we already cloned the config
+          throw new RuntimeException("Could not clone configuration " + configName, e);
+       }
+    }
+}

Copied: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/PojoCacheManagerManagedPojoCache.java (from rev 77668, trunk/cluster/src/main/org/jboss/ha/cachemanager/PojoCacheManagerManagedPojoCache.java)
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/PojoCacheManagerManagedPojoCache.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/cachemanager/PojoCacheManagerManagedPojoCache.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,222 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.ha.cachemanager;
+
+import java.security.AccessController;
+import java.util.Collection;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.CacheException;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.pojo.PojoCache;
+import org.jboss.cache.pojo.PojoCacheException;
+import org.jboss.cache.pojo.PojoCacheThreadContext;
+import org.jboss.logging.Logger;
+import org.jboss.util.loading.ContextClassLoaderSwitcher;
+
+/**
+ * Wrapper around a PojoCache that 1) ensures the calling thread's TCCL doesn't
+ * leak to cache threads via calls to create/start; and 2) logs WARNS about calls
+ * to stop/destroy as these should be handled by the PojoCacheManager.
+ * 
+ * TODO disable calls to stop/destroy once testsuite cleanup code is fixed
+ * to not call those methods.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+class PojoCacheManagerManagedPojoCache implements PojoCache
+{
+   private static final Logger log = Logger.getLogger(PojoCacheManagerManagedPojoCache.class);
+   
+   private final PojoCache delegate;
+   private final ContextClassLoaderSwitcher switcher;
+   
+   PojoCacheManagerManagedPojoCache(PojoCache delegate)
+   {
+      assert delegate != null : "delegate is null";
+      this.delegate = delegate;
+      this.switcher = (ContextClassLoaderSwitcher) AccessController.doPrivileged(ContextClassLoaderSwitcher.INSTANTIATOR);
+   }
+
+   /**
+    * Switches the TCCL to the delegate's classloader before calling create()
+    * on the delegate.
+    */
+   public void create() throws CacheException
+   {
+      ContextClassLoaderSwitcher.SwitchContext switchContext = switcher.getSwitchContext();
+      try
+      {
+         switchContext.setClassLoader(delegate.getClass().getClassLoader());
+         delegate.create();
+      }
+      finally
+      {
+         switchContext.reset();
+      }
+   }
+
+   /**
+    * Switches the TCCL to the delegate's classloader before calling start()
+    * on the delegate.
+    */
+   public void start() throws CacheException
+   {
+      ContextClassLoaderSwitcher.SwitchContext switchContext = switcher.getSwitchContext();
+      try
+      {
+         switchContext.setClassLoader(delegate.getClass().getClassLoader());
+         delegate.start();
+      }
+      finally
+      {
+         switchContext.reset();
+      }
+   }
+
+   /**
+    * TODO: Log a WARN and do nothing else; currently logs and then calls 
+    * through to delegate.
+    */
+   public void stop() throws PojoCacheException
+   {
+      log.warn("stop() should not be directly called on caches obtained from a PojoCacheManager -- use CacheManager.releaseCache()", 
+            new UnsupportedOperationException("stop() is not supported"));
+      delegate.stop();
+   }
+
+   /**
+    * TODO: Log a WARN and do nothing else; currently logs and then calls 
+    * through to delegate.
+    */
+   public void destroy() throws PojoCacheException
+   {
+      log.warn("destroy() should not be directly called on caches obtained from a PojoCacheManager -- use CacheManager.releaseCache()", 
+            new UnsupportedOperationException("destroy() is not supported"));
+      delegate.destroy();
+   }
+   
+   public void addListener(Object arg0)
+   {
+      delegate.addListener(arg0);
+   }
+
+   public void addListener(Object arg0, Pattern arg1)
+   {
+      delegate.addListener(arg0, arg1);
+   }
+
+   public Object attach(String arg0, Object arg1) throws PojoCacheException
+   {
+      return delegate.attach(arg0, arg1);
+   }
+
+   public Object attach(Fqn<?> arg0, Object arg1) throws PojoCacheException
+   {
+      return delegate.attach(arg0, arg1);
+   }
+
+   public Object detach(String arg0) throws PojoCacheException
+   {
+      return delegate.detach(arg0);
+   }
+
+   public Object detach(Fqn<?> arg0) throws PojoCacheException
+   {
+      return delegate.detach(arg0);
+   }
+
+   public boolean exists(Fqn<?> arg0)
+   {
+      return delegate.exists(arg0);
+   }
+
+   public Object find(String arg0) throws PojoCacheException
+   {
+      return delegate.find(arg0);
+   }
+
+   public Object find(Fqn<?> arg0) throws PojoCacheException
+   {
+      return delegate.find(arg0);
+   }
+
+   public Map<Fqn<?>, Object> findAll(String arg0) throws PojoCacheException
+   {
+      return delegate.findAll(arg0);
+   }
+
+   public Map<Fqn<?>, Object> findAll(Fqn<?> arg0) throws PojoCacheException
+   {
+      return delegate.findAll(arg0);
+   }
+
+   public Cache<Object, Object> getCache()
+   {
+      Cache c = delegate.getCache();
+      return c == null ? null : new CacheManagerManagedCache(c);
+   }
+
+   public Collection<Object> getListeners()
+   {
+      return delegate.getListeners();
+   }
+
+   public String getPojoID(Object arg0)
+   {
+      return delegate.getPojoID(arg0);
+   }
+
+   public PojoCacheThreadContext getThreadContext()
+   {
+      return delegate.getThreadContext();
+   }
+
+   public void removeListener(Object arg0)
+   {
+      delegate.removeListener(arg0);
+   }
+
+   // --------------------------------------------------------------  Overrides
+
+   @Override
+   public boolean equals(Object obj)
+   {
+      if (obj instanceof PojoCacheManagerManagedPojoCache)
+      {
+         PojoCacheManagerManagedPojoCache other = (PojoCacheManagerManagedPojoCache) obj;
+         return delegate.equals(other.delegate);
+      }
+      return false;
+   }
+
+   @Override
+   public int hashCode()
+   {
+      return delegate.hashCode();
+   }
+
+}

Copied: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/framework/server/DistributedStateImpl.java (from rev 77655, trunk/cluster/src/main/org/jboss/ha/framework/server/DistributedStateImpl.java)
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/framework/server/DistributedStateImpl.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/framework/server/DistributedStateImpl.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,518 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.ha.framework.server;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.CacheException;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.Node;
+import org.jboss.cache.config.Configuration.CacheMode;
+import org.jboss.cache.notifications.annotation.CacheListener;
+import org.jboss.cache.notifications.annotation.NodeModified;
+import org.jboss.cache.notifications.event.NodeModifiedEvent;
+import org.jboss.cache.notifications.event.NodeModifiedEvent.ModificationType;
+import org.jboss.logging.Logger;
+
+/**
+ *   This class manages distributed state across the cluster.
+ *
+ * @author  <a href="mailto:sacha.labourey at cogito-info.ch">Sacha Labourey</a>.
+ * @author  <a href="mailto:bill at burkecentral.com">Bill Burke</a>.
+ * @author  Scott.Stark at jboss.org
+ * @version $Revision$
+ */
+ at CacheListener
+public class DistributedStateImpl implements DistributedStateImplMBean
+{
+   // Constants -----------------------------------------------------
+
+   public static final String ROOT = "__DISTRIBUTED_STATE__";
+   public static final Fqn ROOTFQN = Fqn.fromElements(ROOT);
+   public static final int ROOTFQNSIZE = ROOTFQN.size();
+   
+   protected static final String SERVICE_NAME = "DistributedState";
+
+   // Attributes ----------------------------------------------------
+
+   protected Map<String, List<Object>> keyListeners = new HashMap<String, List<Object>>();
+   protected Logger log = Logger.getLogger(this.getClass());
+   protected String name = null;
+   protected Cache cache;
+   protected boolean replAsync;
+
+   // Public --------------------------------------------------------
+
+   protected void createService()
+   {
+   }
+
+   public void startService()
+   {
+      if (this.cache == null)
+      {
+         throw new IllegalStateException("No clustered cache available");
+      }
+
+      this.cache.addCacheListener(this);
+   }
+
+   public void stopService()
+   {
+      this.cache.removeCacheListener(this);
+   }
+
+   public void destroyService() throws Exception
+   {
+   }
+
+   public String listContent() throws Exception
+   {
+      StringBuilder result = new StringBuilder();
+      Collection cats = this.getAllCategories();
+      if (cats == null) return result.toString();
+
+      Iterator catsIter = cats.iterator();
+      while (catsIter.hasNext())
+      {
+         String category = (String) catsIter.next();
+         Iterator keysIter = this.getAllKeys(category).iterator();
+
+         result.append("-----------------------------------------------\n");
+         result.append("Logger : ").append(category).append("\n\n");
+         result.append("KEY\t:\tVALUE\n");
+
+         while (keysIter.hasNext())
+         {
+            Serializable key = (Serializable) keysIter.next();
+            String value = this.get(category, key).toString();
+            result.append("'").append(key);
+            result.append("'\t:\t'");
+            result.append(value);
+            result.append("'\n");
+         }
+         result.append("\n");
+      }
+      return result.toString();
+   }
+
+   public String listXmlContent() throws Exception
+   {
+      StringBuilder result = new StringBuilder();
+      result.append("<DistributedState>\n");
+      Collection cats = this.getAllCategories();
+      if (cats != null)
+      {
+         Iterator catsIter = cats.iterator();
+         while (catsIter.hasNext())
+         {
+            String category = (String) catsIter.next();
+            Iterator keysIter = this.getAllKeys(category).iterator();
+
+            result.append("\t<Logger>\n");
+            result.append("\t\t<LoggerName>").append(category).append("</LoggerName>\n");
+
+            while (keysIter.hasNext())
+            {
+               Serializable key = (Serializable) keysIter.next();
+               String value = this.get(category, key).toString();
+               result.append("\t\t<Entry>\n");
+               result.append("\t\t\t<Key>").append(key).append("</Key>\n");
+               result.append("\t\t\t<Value>").append(value).append("</Value>\n");
+               result.append("\t\t</Entry>\n");
+            }
+            result.append("\t</Logger>\n");
+         }
+      }
+
+      result.append("</DistributedState>\n");
+
+      return result.toString();
+   }
+
+   public Cache getClusteredCache()
+   {
+      return this.cache;
+   }
+
+   /**
+    * Sets the cache to use.
+    * 
+    * @param cache the cache
+    * 
+    * @throws IllegalStateException if the cache isn't configured for replication
+    */
+   public void setClusteredCache(Cache<Serializable, Serializable> cache)
+   {
+      this.cache = cache;
+      if (this.cache != null)
+      {
+      	CacheMode cm = cache.getConfiguration().getCacheMode();
+      	if (CacheMode.REPL_ASYNC == cm)
+      	{
+      	   this.replAsync = true;
+      	}
+      	else if (CacheMode.REPL_SYNC == cm)
+      	{
+      	   this.replAsync = false;
+      	}
+      	else
+      	{
+      	   throw new IllegalStateException("Cache must be configured for replication, not " + cm);
+      	}
+      }
+   }
+
+   // DistributedState implementation ----------------------------------------------
+
+   /*
+   * (non-Javadoc)
+   *
+   * @see org.jboss.ha.framework.interfaces.DistributedState#set(java.lang.String,
+   *      java.io.Serializable, java.io.Serializable)
+   */
+   public void set(String category, Serializable key, Serializable value) throws Exception
+   {
+      this.set(category, key, value, true);
+   }
+
+   /*
+    * (non-Javadoc)
+    *
+    * @see org.jboss.ha.framework.interfaces.DistributedState#set(java.lang.String,
+    *      java.io.Serializable, java.io.Serializable, boolean) @param
+    *      asynchronousCall is not supported yet. TreeCache cannot switch this
+    *      on the fly. Will take value from TreeCache-config instead.
+    */
+   public void set(String category, Serializable key, Serializable value, boolean asynchronousCall) throws Exception
+   {
+      if (this.replAsync != asynchronousCall)
+      {
+         if (asynchronousCall)
+         {
+            this.cache.getInvocationContext().getOptionOverrides().setForceAsynchronous(true);
+         }
+         else
+         {
+            this.cache.getInvocationContext().getOptionOverrides().setForceSynchronous(true);
+         }
+      }
+      this.cache.put(this.buildFqn(category), key, value);
+   }
+
+   /*
+     * (non-Javadoc)
+     *
+     * @see org.jboss.ha.framework.interfaces.DistributedState#remove(java.lang.String,
+     *      java.io.Serializable) @return - returns null in case of
+     *      CacheException
+     */
+   public Serializable remove(String category, Serializable key) throws Exception
+   {
+      return this.remove(category, key, true);
+   }
+
+   /*
+    * (non-Javadoc)
+    *
+    * @see org.jboss.ha.framework.interfaces.DistributedState#remove(java.lang.String,
+    *      java.io.Serializable, boolean)
+    */
+   public Serializable remove(String category, Serializable key, boolean asynchronousCall) throws Exception
+   {
+      Serializable retVal = this.get(category, key);
+      if (retVal != null)
+      {
+         if (this.replAsync != asynchronousCall)
+         {
+            if (asynchronousCall)
+            {
+               this.cache.getInvocationContext().getOptionOverrides().setForceAsynchronous(true);
+            }
+            else
+            {
+               this.cache.getInvocationContext().getOptionOverrides().setForceSynchronous(true);
+            }
+         }
+         this.cache.remove(this.buildFqn(category), key);
+      }
+      return retVal;
+   }
+
+   /*
+     * (non-Javadoc)
+     *
+     * @see org.jboss.ha.framework.interfaces.DistributedState#get(java.lang.String,
+     *      java.io.Serializable)
+     */
+   public Serializable get(String category, Serializable key)
+   {
+      try
+      {
+         return (Serializable) this.cache.get(this.buildFqn(category), key);
+      }
+      catch (CacheException ce)
+      {
+         return null;
+      }
+   }
+
+   public Collection getAllCategories()
+   {
+      try
+      {
+         Node base = this.cache.getRoot().getChild(ROOTFQN);
+         Collection keys = (base == null ? null : base.getChildrenNames());
+         if (keys != null && keys.size() > 0)
+         {
+            keys = Collections.unmodifiableCollection(keys);
+         }
+         return keys;
+      }
+      catch (CacheException ce)
+      {
+         return null;
+      }
+   }
+
+   /*
+     * (non-Javadoc)
+     *
+     * @see org.jboss.ha.framework.interfaces.DistributedState#getAllKeys(java.lang.String)
+     *      @return - returns null in case of CacheException
+     */
+   public Collection getAllKeys(String category)
+   {
+      try
+      {
+         Node<Serializable, Serializable> node = this.getNode(category);
+         if (node == null) return null;
+         return node.getKeys();
+      }
+      catch (CacheException ce)
+      {
+         return null;
+      }
+   }
+
+   /*
+     * (non-Javadoc)
+     *
+     * @see org.jboss.ha.framework.interfaces.DistributedState#getAllValues(java.lang.String)
+     *      @return - returns null in case of CacheException
+     */
+   public Collection getAllValues(String category)
+   {
+      try
+      {
+         Node categoryNode = this.getNode(category);
+         if (categoryNode == null) return null;
+         Set childNodes = categoryNode.getKeys();
+         if (childNodes == null) return null;
+         Map entries = categoryNode.getData();
+         if (entries == null) return null;
+         Collection retVal = new HashSet(entries.values());
+         return Collections.unmodifiableCollection(retVal);
+      }
+      catch (CacheException ce)
+      {
+         return null;
+      }
+   }
+
+   public void registerDSListenerEx(String category, DSListenerEx subscriber)
+   {
+      this.registerListener(category, subscriber);
+   }
+
+   public void unregisterDSListenerEx(String category, DSListenerEx subscriber)
+   {
+      this.unregisterListener(category, subscriber);
+   }
+
+   public void registerDSListener(String category, DSListener subscriber)
+   {
+      this.registerListener(category, subscriber);
+   }
+
+   public void unregisterDSListener(String category, DSListener subscriber)
+   {
+      this.unregisterListener(category, subscriber);
+   }
+
+   // Package protected ---------------------------------------------
+
+   // Protected -----------------------------------------------------
+
+   protected void registerListener(String category, Object subscriber)
+   {
+      synchronized (this.keyListeners)
+      {
+         List<Object> listeners = this.keyListeners.get(category);
+         if (listeners == null)
+         {
+            listeners = new ArrayList<Object>();
+            this.keyListeners.put(category, listeners);
+         }
+         listeners.add(subscriber);
+      }
+   }
+
+   protected void unregisterListener(String category, Object subscriber)
+   {
+      synchronized (this.keyListeners)
+      {
+         List<Object> listeners = this.keyListeners.get(category);
+         if (listeners == null) return;
+
+         listeners.remove(subscriber);
+         if (listeners.size() == 0)
+         {
+            this.keyListeners.remove(category);
+         }
+      }
+   }
+
+   protected void notifyKeyListeners(String category, Serializable key, Serializable value, boolean locallyModified)
+   {
+      synchronized (this.keyListeners)
+      {
+         List<Object> listeners = this.keyListeners.get(category);
+         if (listeners == null) return;
+         String strKey = key.toString();
+
+         for (Object listener: listeners)
+         {
+            if (listener instanceof DSListener)
+            {
+               DSListener dslistener = (DSListener) listener;
+               dslistener.valueHasChanged(category, strKey, value, locallyModified);
+            }
+            else
+            {
+               DSListenerEx dslistener = (DSListenerEx) listener;
+               dslistener.valueHasChanged(category, key, value, locallyModified);
+            }
+         }
+      }
+   }
+
+   protected void notifyKeyListenersOfRemove(String category, Serializable key, Serializable oldContent,
+         boolean locallyModified)
+   {
+      synchronized (this.keyListeners)
+      {
+         List<Object> listeners = this.keyListeners.get(category);
+         if (listeners == null) return;
+         String strKey = key.toString();
+
+         for (Object listener: listeners)
+         {
+            if (listener instanceof DSListener)
+            {
+               DSListener dslistener = (DSListener) listener;
+               dslistener.keyHasBeenRemoved(category, strKey, oldContent, locallyModified);
+            }
+            else
+            {
+               DSListenerEx dslistener = (DSListenerEx) listener;
+               dslistener.keyHasBeenRemoved(category, key, oldContent, locallyModified);
+            }
+         }
+      }
+   }
+
+   protected void cleanupKeyListeners()
+   {
+      // NOT IMPLEMENTED YET
+   }
+
+   /** ExtendedTreeCacheListener methods */
+
+   // Private -------------------------------------------------------
+   protected Fqn buildFqn(String category)
+   {
+      return Fqn.fromRelativeElements(ROOTFQN, category);
+   }
+
+   protected Fqn buildFqn(String category, Serializable key)
+   {
+      return Fqn.fromElements(ROOT, category, key);
+   }
+
+   protected Fqn buildFqn(String category, Serializable key, Serializable value)
+   {
+      return Fqn.fromElements(ROOT, category, key, value);
+   }
+
+   protected Node getNode(String category) throws CacheException
+   {
+      return this.cache.getRoot().getChild(this.buildFqn(category));
+   }
+
+   // @CacheListener  -------------------------------------------------
+
+   @NodeModified
+   public void nodeModified(NodeModifiedEvent event)
+   {
+      if (event.isPre()) return;
+
+      // we're only interested in put and remove data operations
+      ModificationType modType = event.getModificationType();
+      if (!modType.equals(ModificationType.PUT_DATA) && !modType.equals(ModificationType.REMOVE_DATA)) return;
+
+      // ignore changes for other roots in a shared cache
+      Fqn fqn = event.getFqn();
+      if (!fqn.isChildOf(ROOTFQN)) return;
+
+      Serializable key = null;
+      Serializable value = null;
+
+      // there should be exactly one key/value pair in the map
+      Map data = event.getData();
+      if (data != null && !data.isEmpty())
+      {
+         key = (Serializable) data.keySet().iterator().next();
+         value = (Serializable) data.get(key);
+      }
+
+      if (modType.equals(ModificationType.PUT_DATA))
+      {
+         DistributedStateImpl.this.notifyKeyListeners((String) fqn.get(ROOTFQNSIZE), key, value, event.isOriginLocal());
+      }
+      else
+      {
+         DistributedStateImpl.this.notifyKeyListenersOfRemove((String) fqn.get(ROOTFQNSIZE), key, value, event.isOriginLocal());
+      }
+   }
+
+}

Copied: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/framework/server/DistributedStateImplMBean.java (from rev 77655, trunk/cluster/src/main/org/jboss/ha/framework/server/DistributedStateImplMBean.java)
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/framework/server/DistributedStateImplMBean.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/framework/server/DistributedStateImplMBean.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,43 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.ha.framework.server;
+
+
+/**
+ * MBean interface for the Distributed State (DS) service
+ *  
+ * @author  <a href="mailto:sacha.labourey at cogito-info.ch">Sacha Labourey</a>.
+ * @version $Revision$
+ *   
+ * <p><b>Revisions:</b>
+ *
+ * <p><b>29. d�cembre 2001 Sacha Labourey:</b>
+ * <ul>
+ * <li> First implementation </li> 
+ * </ul>
+ */
+public interface DistributedStateImplMBean
+   extends org.jboss.ha.framework.interfaces.DistributedState
+{
+   String listContent () throws Exception;
+   String listXmlContent () throws Exception;
+}

Copied: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/jndi/impl/jbc/JBossCacheDistributedTreeManager.java (from rev 77666, trunk/cluster/src/main/org/jboss/ha/jndi/impl/jbc/JBossCacheDistributedTreeManager.java)
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/jndi/impl/jbc/JBossCacheDistributedTreeManager.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/jndi/impl/jbc/JBossCacheDistributedTreeManager.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,569 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.ha.jndi.impl.jbc;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.InvalidNameException;
+import javax.naming.Name;
+import javax.naming.NameAlreadyBoundException;
+import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import javax.naming.NotContextException;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.CacheException;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.Node;
+import org.jboss.ha.jndi.spi.DistributedTreeManager;
+import org.jboss.logging.Logger;
+import org.jnp.interfaces.Naming;
+import org.jnp.interfaces.NamingContext;
+import org.jnp.interfaces.NamingParser;
+
+/**
+ *  This class utilizes JBossCache to provide a DistributedTreeManager implementation.
+ *
+ *  @author <a href="mailto:jgauthier at novell.com">Jerry Gauthier</a>
+ *  @author Brian Stansberry
+ *  
+ *  @version $Revision: 74736 $
+ */
+public class JBossCacheDistributedTreeManager
+   implements org.jnp.interfaces.Naming, DistributedTreeManager
+{
+   static final long serialVersionUID = 6342802270002172451L;
+   
+   private static Logger log = Logger.getLogger(JBossCacheDistributedTreeManager.class);
+
+   private static final NamingParser parser = new NamingParser();
+  
+   public static final String DEFAULT_ROOT = "__HA_JNDI__";
+   
+   // Attributes --------------------------------------------------------
+
+   private Cache<String, Binding> m_cache;
+   private Fqn<String> m_root;
+   private Naming haStub;
+   private boolean treeRootSet;
+
+   // Constructor --------------------------------------------------------
+  
+   public JBossCacheDistributedTreeManager()
+   {      
+      super();
+   }
+   
+   // JBossCacheDistributedTreeManagerMBean ----------------------------------
+   
+   public Cache getClusteredCache()
+   {
+      return m_cache;
+   }
+   
+   public void setClusteredCache(Cache cache)
+   {
+      if (treeRootSet)
+      {
+         throw new IllegalStateException("Cannot change clusteredCache after call to init()");
+      }
+      this.m_cache = cache;
+   }
+   
+   public String getRootFqn()
+   {
+      return m_root == null ? DEFAULT_ROOT : m_root.toString();
+   }
+   
+   public void setRootFqn(String rootFqn)
+   {
+      if (treeRootSet)
+      {
+         throw new IllegalStateException("Cannot change rootFqn after call to init()");
+      }
+      
+      m_root = (rootFqn == null) ? null : Fqn.fromString(rootFqn);         
+   }
+   
+   // Public -----------------------------------------------------------------
+
+   public void init()
+   {
+      if (this.haStub == null)
+      {
+         throw new IllegalStateException("Must call setHAStub before starting");
+      }
+      
+      if (this.m_cache == null)
+      {
+         throw new IllegalStateException("Must call setClusteredCache before starting");
+      }
+      
+      log.debug("initializing HAJNDITreeCache root");
+      this.putTreeRoot();
+   }
+
+   public void shutdown()
+   {
+   }
+
+   public Naming getHAStub()
+   {
+      return this.haStub;
+   }
+
+   public void setHAStub(Naming stub)
+   {
+      this.haStub = stub;
+   }
+
+   // Naming implementation -----------------------------------------
+  
+   public void bind(Name name, Object obj, String className) throws NamingException
+   {
+      if (log.isTraceEnabled())
+      {
+         log.trace("bind, name="+name);
+      }
+      
+      this.internalBind(name, obj, className, false);
+   }
+   
+   public void rebind(Name name, Object obj, String className) throws NamingException
+   {
+      if (log.isTraceEnabled())
+      {
+         log.trace("rebind, name="+name);
+      }
+
+      this.internalBind(name, obj, className, true);
+   }
+
+   public void unbind(Name name) throws NamingException
+   {
+      if (log.isTraceEnabled())
+      {
+         log.trace("unbind, name="+name);
+      }
+      if (name.isEmpty())
+      {
+         // Empty names are not allowed
+         throw new InvalidNameException();
+      }
+      
+      // is the name a context?
+      try
+      {
+         Fqn<String> temp = new Fqn<String>(this.m_root, Fqn.fromString(name.toString()));
+         // TODO why not jst call remove -- why hasChild first?
+         if (this.m_cache.getRoot().hasChild(temp))
+         {
+            this.m_cache.removeNode(temp);
+            return;
+         }
+      }
+      catch (CacheException ce)
+      {
+         // don't chain CacheException since JBoss Cache may not be on remote client's classpath
+         NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
+         ne.setStackTrace(ce.getStackTrace());
+         throw ne;
+      }
+      
+      int size = name.size();
+      
+      // get the context and key
+      Fqn<String> ctx;
+      String key = name.get(size - 1);
+      if (size > 1) // find subcontext to which the key is bound
+      {
+         String prefix = name.getPrefix(size - 1).toString();
+         Fqn<String> fqn = Fqn.fromString(prefix);
+         ctx = new Fqn<String>(this.m_root, fqn);
+      }
+      else
+      {
+         ctx = this.m_root;
+      }
+      
+      try
+      {
+         Object removed = this.m_cache.remove(ctx, key);
+         if (removed == null)
+         {
+            if (!this.m_cache.getRoot().hasChild(ctx))
+            {
+                throw new NotContextException(name.getPrefix(size - 1).toString() + " not a context");
+            }
+
+            throw new NameNotFoundException(key + " not bound");
+         }
+      }
+      catch (CacheException ce)
+      {
+         // don't chain CacheException since JBoss Cache may not be on remote client's classpath
+         NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
+         ne.setStackTrace(ce.getStackTrace());
+         throw ne;
+      }
+   }
+   
+   public Object lookup(Name name) throws NamingException
+   {
+      boolean trace = log.isTraceEnabled();
+      if (trace)
+      {
+         log.trace("lookup, name="+name);
+      }
+   
+      if (name.isEmpty())
+      {
+         // Return this
+         return new NamingContext(null, parser.parse(""), this.getHAStub());
+      }
+
+      // is the name a context?
+      try
+      {
+         Node<String, Binding> n = this.m_cache.getRoot().getChild(new Fqn<String>(this.m_root, Fqn.fromString(name.toString())));
+         if (n != null)
+         {
+            Name fullName = (Name) name.clone();
+            return new NamingContext(null, fullName, this.getHAStub());
+         }
+      }
+      catch (CacheException ce)
+      {
+         // don't chain CacheException since JBoss Cache may not be on remote client's classpath
+         NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
+         ne.setStackTrace(ce.getStackTrace());
+         throw ne;
+      }
+   
+      int size = name.size();
+   
+      // get the context and key
+      Fqn<String> ctx;
+      String key = name.get(size - 1);
+      if (size > 1) // find subcontext to which the key is bound
+      {
+         String prefix = name.getPrefix(size - 1).toString();
+         Fqn<String> fqn = Fqn.fromString(prefix);
+         ctx = new Fqn<String>(this.m_root, fqn);
+      }
+      else
+      {
+         ctx = this.m_root;
+      }
+   
+      try
+      {
+         Binding b = this.m_cache.get(ctx, key);
+         
+         // if key not in cache, return null
+         return (b != null) ? b.getObject() : null;
+      }
+      catch (CacheException ce)
+      {
+         // don't chain CacheException since JBoss Cache may not be on remote client's classpath
+         NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
+         ne.setStackTrace(ce.getStackTrace());
+         throw ne;
+      }
+   }
+   
+   public Collection<NameClassPair> list(Name name) throws NamingException
+   {
+      if (log.isTraceEnabled())
+      {
+         log.trace("list, name="+name);
+      }
+   
+      // get the context
+      Fqn<String> ctx;
+      String ctxName = "";
+      int size = name.size();
+      if (size >= 1)
+      {
+         ctxName = name.toString();
+         Fqn<String> fqn = Fqn.fromString(ctxName);
+         ctx = new Fqn<String>(this.m_root, fqn);
+      }
+      else
+      {
+         ctx = this.m_root;
+      }
+      
+      boolean exists = this.m_cache.getRoot().hasChild(ctx);
+      if (!exists)
+      {
+         try
+         {
+            return Collections.list(new InitialContext().list(name));
+         }
+         catch (NamingException e)
+         {
+            throw new NotContextException(ctxName+ " not a context");
+         }
+      }
+      
+      try
+      {
+         List<NameClassPair> list = new LinkedList<NameClassPair>();
+
+         Node<String, Binding> base = this.m_cache.getRoot().getChild(ctx);
+         if (base != null)
+         {
+            for (Binding b: base.getData().values())
+            {
+               list.add(new NameClassPair(b.getName(),b.getClassName(),true));
+            }
+            
+            // Why doesn't this return Set<String>?
+            Set<Object> children = base.getChildrenNames();
+            if (children != null && !children.isEmpty())
+            {
+               for (Object child: children)
+               {
+                  String node = (String) child;
+                  Name fullName = (Name) name.clone();
+                  fullName.add(node);
+                  list.add(new NameClassPair(node, NamingContext.class.getName(),true));
+               }
+            }
+         }
+         
+         return list;
+      }
+      catch (CacheException ce)
+      {
+         // don't chain CacheException since JBoss Cache may not be on remote client's classpath
+         NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
+         ne.setStackTrace(ce.getStackTrace());
+         throw ne;
+      }
+   }
+
+   public Collection<Binding> listBindings(Name name) throws NamingException
+   {
+      if (log.isTraceEnabled())
+      {
+         log.trace("listBindings, name="+name);
+      }
+      
+      // get the context
+      Fqn<String> ctx;
+      String ctxName = "";
+      int size = name.size();
+      if (size >= 1)
+      {
+         ctxName = name.toString();
+         Fqn<String> fqn = Fqn.fromString(ctxName);
+         ctx = new Fqn<String>(this.m_root, fqn);
+      }
+      else
+      {
+         ctx = this.m_root;
+      }
+      
+      boolean exists = this.m_cache.getRoot().hasChild(ctx);
+      if (!exists)
+      {
+         // not found in global jndi, look in local.
+         try
+         {
+            return Collections.list(new InitialContext().listBindings(name));
+         }
+         catch (NamingException e)
+         {
+            throw new NotContextException(ctxName+ " not a context");
+         }
+      }
+      
+      try
+      {
+         List<Binding> list = new LinkedList<Binding>();
+         
+         Node<String, Binding> node = this.m_cache.getRoot().getChild(ctx);
+         if (node != null)
+         {
+            Map<String, Binding> data = node.getData();
+            if (data != null && !data.isEmpty())
+            {
+               list.addAll(data.values());
+            }
+            
+            // Why doesn't this return Set<String>?
+            Set<Object> children = node.getChildrenNames();
+            if (children != null && !children.isEmpty())
+            {
+               for (Object obj: children)
+               {
+                  String child = (String) obj;
+                  Name fullName = (Name) name.clone();
+                  fullName.add(child);
+                  NamingContext subCtx = new NamingContext(null, fullName, this.getHAStub());
+                  list.add(new Binding(child, NamingContext.class.getName(), subCtx, true));
+               }
+            }
+         }
+         
+         return list;
+      }
+      catch (CacheException ce)
+      {
+         // don't chain CacheException since JBoss Cache may not be on remote client's classpath
+         NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
+         ne.setStackTrace(ce.getStackTrace());
+         throw ne;
+      }
+   }
+
+   public Context createSubcontext(Name name) throws NamingException
+   {
+      if (log.isTraceEnabled())
+      {
+         log.trace("createSubcontext, name="+name);
+      }
+      
+      int size = name.size();
+      
+      if (size == 0)
+      {
+         throw new InvalidNameException("Cannot pass an empty name to createSubcontext");
+      }
+
+      // does the new context already exist?
+      String str = name.toString();
+      Fqn<String> fqn = Fqn.fromString(str);
+      Fqn<String> ctx = new Fqn<String>(this.m_root, fqn);
+      if (this.m_cache.getRoot().hasChild(ctx))
+      {
+         throw new NameAlreadyBoundException();
+      }
+      
+      // does the prefix context already exist?
+      Fqn<String> pctx;
+      String newctx = name.get(size - 1);
+      if (size > 1) // find subcontext to which the context will be added
+      {
+         String prefix = name.getPrefix(size - 1).toString();
+         Fqn<String> fqn2 = Fqn.fromString(prefix);
+         pctx = new Fqn<String>(this.m_root, fqn2);
+      }
+      else
+      {
+         pctx = this.m_root;
+      }
+      
+      boolean exists = this.m_cache.getRoot().hasChild(pctx);
+      if (!exists)
+      {
+         throw new NotContextException(name.getPrefix(size - 1).toString());
+      }
+
+      Fqn<String> newf = new Fqn<String>(pctx, Fqn.fromString(newctx));
+      try
+      {
+         this.m_cache.put(newf, new HashMap<String, Binding>());
+      }
+      catch (CacheException ce)
+      {
+         // don't chain CacheException since JBoss Cache may not be on remote client's classpath
+         NamingException ne = new NamingException(ce.getClass().getName() + ": " + ce.getMessage());
+         ne.setStackTrace(ce.getStackTrace());
+         throw ne;
+      }
+   
+      Name fullName = parser.parse("");
+      fullName.addAll(name);
+      
+      return new NamingContext(null, fullName, this.getHAStub());
+   }
+   
+   private void putTreeRoot() throws CacheException
+   {
+      if (this.m_root == null)
+      {
+         setRootFqn(DEFAULT_ROOT);
+      }
+      
+      if (!this.m_cache.getRoot().hasChild(this.m_root))
+      {
+         this.m_cache.put(this.m_root, null);
+         this.treeRootSet = true;
+      }
+   }
+  
+   private void internalBind(Name name, Object obj, String className, boolean rebind) throws NamingException
+   {
+      if (name.isEmpty())
+      {  // Empty names are not allowed
+         throw new InvalidNameException();
+      }
+
+      int size = name.size();
+      
+      // get the context and key
+      Fqn<String> ctx;
+      String key = name.get(size - 1);
+      if (size > 1) // find subcontext to which the key will be added
+      {
+         String prefix = name.getPrefix(size - 1).toString();
+         Fqn<String> fqn = Fqn.fromString(prefix);
+         ctx = new Fqn<String>(this.m_root, fqn);
+      }
+      else
+      {
+         ctx = this.m_root;
+      }
+   
+      boolean exists = this.m_cache.getRoot().hasChild(ctx);
+      if (!exists)
+      {
+         throw new NotContextException(name.getPrefix(size - 1).toString() + " not a context");
+         // note - NamingServer throws a CannotProceedException if the client attempts to bind
+         //        to a Reference object having an "nns" address.  This implementation simply
+         //        throws the NotContextException that's used when "nns" isn't present.
+      }
+      if (!rebind)
+      {
+         Node<String, Binding> node = this.m_cache.getRoot().getChild(ctx);
+         if ((node != null) && (node.get(key) != null))
+         {
+            throw new NameAlreadyBoundException(key);
+         }
+      }
+      
+      this.m_cache.put(ctx, key, new Binding(key, className, obj, true));
+   }
+}

Copied: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/jndi/impl/jbc/JBossCacheDistributedTreeManagerMBean.java (from rev 77666, trunk/cluster/src/main/org/jboss/ha/jndi/impl/jbc/JBossCacheDistributedTreeManagerMBean.java)
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/jndi/impl/jbc/JBossCacheDistributedTreeManagerMBean.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/ha/jndi/impl/jbc/JBossCacheDistributedTreeManagerMBean.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,41 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.ha.jndi.impl.jbc;
+
+import org.jboss.cache.Cache;
+
+/**
+ * StandardMBean interface for JBossCacheDistributedTreeManager.
+ * 
+ * @author Brian Stansberry
+ */
+public interface JBossCacheDistributedTreeManagerMBean
+{
+   Cache getClusteredCache();
+   
+   void setClusteredCache(Cache cache);
+   
+   String getRootFqn();
+   
+   void setRootFqn(String rootFqn);
+}

Copied: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/DistributedCacheManagerFactoryImpl.java (from rev 77655, trunk/tomcat/src/main/org/jboss/web/tomcat/service/session/distributedcache/impl/DistributedCacheManagerFactoryImpl.java)
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/DistributedCacheManagerFactoryImpl.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/DistributedCacheManagerFactoryImpl.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,227 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.session.distributedcache.impl;
+
+import java.io.File;
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerInvocationHandler;
+import javax.management.ObjectName;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.config.Configuration;
+import org.jboss.cache.pojo.PojoCache;
+import org.jboss.cache.pojo.PojoCacheFactory;
+import org.jboss.cache.pojo.jmx.PojoCacheJmxWrapper;
+import org.jboss.cache.pojo.jmx.PojoCacheJmxWrapperMBean;
+import org.jboss.cache.transaction.BatchModeTransactionManagerLookup;
+import org.jboss.web.tomcat.service.session.distributedcache.impl.jbc.FieldBasedJBossCacheService;
+import org.jboss.web.tomcat.service.session.distributedcache.impl.jbc.JBossCacheService;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.ClusteringNotSupportedException;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.FieldBasedDistributedCacheManager;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.TomcatClusterConfig;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.TomcatClusterDistributedCacheManagerFactory;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class DistributedCacheManagerFactoryImpl implements TomcatClusterDistributedCacheManagerFactory
+{
+
+   public static final String DEFAULT_CLUSTER_NAME = "Tomcat-Cluster";
+
+   /** TreeCache's isolation level */
+   public static final String DEFAULT_ISOLATION_LEVEL = "REPEATABLE_READ";
+   
+   /** TreeCache's cache mode */
+   public static final String DEFAULT_CACHE_MODE = "REPL_ASYNC";
+   
+   /** TreeCache's lock aquisition timeout */
+   public static final long DEFAULT_LOCK_TIMEOUT = 15000;
+   
+   /** TransactionManagerLookup implementation that the TreeCache should use. */
+   public static final String DEFAULT_TM_LOOKUP = 
+      BatchModeTransactionManagerLookup.class.getName();
+   
+   private TomcatClusterConfig tomcatConfig;
+   private MBeanServer mserver;
+   private boolean pojoCacheLocal = false;
+   
+   private PojoCache pojoCache;
+   private Cache plainCache;
+   
+   public DistributedCacheManager getDistributedCacheManager(String cacheConfigName)
+         throws ClusteringNotSupportedException
+   {
+      return plainCache == null? new JBossCacheService(cacheConfigName) : new JBossCacheService(plainCache);
+   }
+
+   public FieldBasedDistributedCacheManager getFieldBasedDistributedCacheManager(String cacheConfigName)
+         throws ClusteringNotSupportedException
+   {
+      return pojoCache == null? new FieldBasedJBossCacheService(cacheConfigName) : new FieldBasedJBossCacheService(pojoCache);
+   }
+
+   public TomcatClusterConfig getTomcatClusterConfig()
+   {
+      return tomcatConfig;
+   }
+
+   public void setTomcatClusterConfig(TomcatClusterConfig clusterConfig)
+   {
+      this.tomcatConfig = clusterConfig;
+   }
+
+   public void start() throws Exception
+   {
+      if (tomcatConfig != null)
+      {
+         initializePojoCache();
+      }      
+   }
+
+   public void stop() throws Exception
+   {
+      if (pojoCache != null)
+      {
+         pojoCache.stop();
+         pojoCache.destroy();
+         
+         if (pojoCacheLocal && mserver != null && tomcatConfig.getCacheObjectName() != null)
+         {
+            mserver.unregisterMBean(new ObjectName(tomcatConfig.getCacheObjectName()));
+         }
+      }      
+   }
+
+   public PojoCache getPojoCache()
+   {
+      return pojoCache;
+   }
+
+   /** 
+    * Hook for test fixtures to inject a PojoCache, which if present will
+    * be used to create the DistributedCacheManager in preference to any
+    * passed <code>cacheConfigName</code>.
+    */
+   public void setPojoCache(PojoCache pojoCache)
+   {
+      this.pojoCache = pojoCache;
+      this.plainCache = pojoCache.getCache();
+   }
+
+   public Cache getPlainCache()
+   {
+      return plainCache;
+   }
+
+   /** 
+    * Hook for test fixtures to inject a Cache, which if present will
+    * be used to create the DistributedCacheManager in preference to any
+    * passed <code>cacheConfigName</code>.
+    */
+   public void setPlainCache(Cache plainCache)
+   {
+      this.plainCache = plainCache;
+      this.pojoCache = null;
+   }
+   
+   /**
+    * Convenience method for test fixtures to clear any injected cache.
+    */
+   public void clearCaches()
+   {
+      this.plainCache = null;
+      this.pojoCache = null;
+   }
+   
+   
+   /**
+    * Gets our PojCache, either from a local reference or the JMX
+    * server.  If one is not found, creates and configures it.
+    */
+   private void initializePojoCache() throws Exception
+   {
+      if (pojoCache == null) {
+         
+         PojoCacheJmxWrapperMBean pcWrapper = null;
+         MBeanServer server = tomcatConfig.getMBeanServer();
+         String cfgName = tomcatConfig.getCacheObjectName();
+         ObjectName objName = cfgName == null ? null : new ObjectName(tomcatConfig.getCacheObjectName());
+         if (server != null && objName != null && server.isRegistered(objName))
+         {
+            // Get a proxy to the existing TreeCache
+            pcWrapper = ((PojoCacheJmxWrapperMBean) 
+                           MBeanServerInvocationHandler.newProxyInstance(server, objName, PojoCacheJmxWrapperMBean.class, false));
+         }
+         else
+         {
+            // See if there is an XML descriptor file to configure the cache
+            File configFile = tomcatConfig.getCacheConfigFile();
+            String clusterName = tomcatConfig.getClusterName();
+            
+            if (configFile != null)
+            {
+               pcWrapper = new PojoCacheJmxWrapper(PojoCacheFactory.createCache(configFile.getAbsolutePath(), false));
+               Configuration config = pojoCache.getCache().getConfiguration();
+                              
+               if (clusterName != null)
+               {
+                  // Override the XML config with the name provided in
+                  // server.xml.  Method setClusterName is specified in the
+                  // Cluster interface, otherwise we would not do this
+                  config.setClusterName(clusterName);
+               }
+            }
+            else
+            {
+               // User did not try to configure the cache.
+               // Configure it using defaults.  Only exception
+               // is the clusterName, which user can specify in server.xml.
+               Configuration config = new Configuration();
+               String channelName = (clusterName == null) ? DEFAULT_CLUSTER_NAME
+                                                          : clusterName;
+               config.setClusterName(channelName);
+               config.setIsolationLevel(DEFAULT_ISOLATION_LEVEL);
+               config.setCacheMode(DEFAULT_CACHE_MODE);
+               config.setLockAcquisitionTimeout(DEFAULT_LOCK_TIMEOUT);
+               config.setTransactionManagerLookupClass(DEFAULT_TM_LOOKUP);
+               
+               pcWrapper = new PojoCacheJmxWrapper(PojoCacheFactory.createCache(config, false));
+            }
+            
+            if (server != null && objName != null)
+            {
+               server.registerMBean(pcWrapper, objName);
+            }
+            
+            pojoCacheLocal = true;
+         }
+         
+         setPojoCache(pcWrapper.getPojoCache());
+      }
+   }
+   
+}

Added: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/BatchingManagerImpl.java
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/BatchingManagerImpl.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/BatchingManagerImpl.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,96 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.session.distributedcache.impl.jbc;
+
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.TransactionManager;
+
+import org.jboss.logging.Logger;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.BatchingManager;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+class BatchingManagerImpl implements BatchingManager
+{
+   private static final Logger log = Logger.getLogger(BatchingManagerImpl.class);
+   
+   private final TransactionManager tm;
+   
+   BatchingManagerImpl(TransactionManager tm)
+   {
+      assert tm != null : " batchingManager is null";
+      
+      this.tm = tm;
+   }
+
+   public boolean isBatchInProgress() throws Exception
+   {
+      return (tm.getTransaction() != null);
+   }
+
+   public void startBatch() throws Exception
+   {
+      tm.begin();
+   }
+
+   public void setBatchRollbackOnly() throws Exception
+   {
+      tm.setRollbackOnly();
+   }
+
+   public void endBatch()
+   {
+      try
+      {
+         if(tm.getTransaction().getStatus() != Status.STATUS_MARKED_ROLLBACK)
+         {
+            tm.commit();
+         }
+         else
+         {
+            log.debug("endBatch(): rolling back batch");
+            tm.rollback();
+         }
+      }
+      catch (RollbackException re)
+      {
+         // Do nothing here since cache may rollback automatically.
+         log.warn("endBatch(): rolling back transaction with exception: " +re);
+      }
+      catch (RuntimeException re)
+      {
+         throw re;
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException("endTransaction(): Caught Exception ending batch: ", e);
+      }
+      
+   }
+   
+   
+   
+}

Added: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/CacheListener.java
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/CacheListener.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/CacheListener.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,188 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.web.tomcat.service.session.distributedcache.impl.jbc;
+
+import java.util.Map;
+
+import org.jboss.cache.Fqn;
+import org.jboss.cache.notifications.annotation.NodeModified;
+import org.jboss.cache.notifications.annotation.NodeRemoved;
+import org.jboss.cache.notifications.event.NodeModifiedEvent;
+import org.jboss.cache.notifications.event.NodeRemovedEvent;
+import org.jboss.logging.Logger;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionMetadata;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionTimestamp;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.LocalDistributableSessionManager;
+
+/**
+ * Listens for removals and modifications in the cache, notifying the 
+ * session manager of significant events.
+ * 
+ * @author Brian Stansberry
+ */
+ at org.jboss.cache.notifications.annotation.CacheListener
+public class CacheListener extends CacheListenerBase
+{
+   // Element within an FQN that is the root of a Pojo attribute map
+   private static final int POJO_ATTRIBUTE_FQN_INDEX = SESSION_ID_FQN_INDEX + 1;
+   // Element within an FQN that is the root of an individual Pojo attribute
+   private static final int POJO_KEY_FQN_INDEX = POJO_ATTRIBUTE_FQN_INDEX + 1;
+   // Element within an FQN that is the root of a session's internal pojo storage area
+   private static final int POJO_INTERNAL_FQN_INDEX = SESSION_ID_FQN_INDEX + 1;
+   // Minimum size of an FQN that is below the root of a session's internal pojo storage area
+   private static final int POJO_INTERNAL_FQN_SIZE = POJO_INTERNAL_FQN_INDEX + 1;
+   private static Logger log_ = Logger.getLogger(CacheListener.class);
+   private boolean fieldBased_;
+
+   CacheListener(JBossCacheWrapper wrapper, LocalDistributableSessionManager manager, String hostname, String webapp, boolean field)
+   {      
+      super(manager, hostname, webapp);
+      fieldBased_ = field;
+   }
+
+   // --------------- CacheListener methods ------------------------------------
+
+   @NodeRemoved
+   public void nodeRemoved(NodeRemovedEvent event)
+   {      
+      if (event.isPre())
+         return;
+      
+      boolean local = event.isOriginLocal();
+      if (!fieldBased_ && local)
+         return;
+      
+      Fqn fqn = event.getFqn();
+      boolean isBuddy = isBuddyFqn(fqn);
+      
+      if (!local 
+            && isFqnSessionRootSized(fqn, isBuddy) 
+            && isFqnForOurWebapp(fqn, isBuddy))
+      {
+         // A session has been invalidated from another node;
+         // need to inform manager
+         String sessId = getIdFromFqn(fqn, isBuddy);
+         manager_.notifyRemoteInvalidation(sessId);
+      }
+      else if (local && !isBuddy
+                  && isPossibleInternalPojoFqn(fqn) 
+                  && isFqnForOurWebapp(fqn, isBuddy))
+      {
+         // One of our sessions' pojos is modified; need to inform
+         // the manager so it can mark the session dirty
+         String sessId = getIdFromFqn(fqn, isBuddy);
+         manager_.notifyLocalAttributeModification(sessId);
+      }
+   }
+   
+   @NodeModified
+   public void nodeModified(NodeModifiedEvent event)
+   {      
+      if (event.isPre())
+         return;
+      
+      boolean local = event.isOriginLocal();
+      if (!fieldBased_ && local)
+         return;
+      
+      Fqn fqn = event.getFqn();
+      boolean isBuddy = isBuddyFqn(fqn);      
+      
+      if (!local 
+             &&isFqnSessionRootSized(fqn, isBuddy)
+             &&isFqnForOurWebapp(fqn, isBuddy))
+      {
+         // Query if we have version value in the distributed cache. 
+         // If we have a version value, compare the version and invalidate if necessary.
+         Map data = event.getData();
+         Integer version = (Integer) data.get(JBossCacheService.VERSION_KEY);
+         if(version != null)
+         {
+            String realId = getIdFromFqn(fqn, isBuddy);
+            String owner = isBuddy ? getBuddyOwner(fqn) : null;
+            // Notify the manager that a session has been updated
+            boolean updated = manager_.sessionChangedInDistributedCache(realId, owner, 
+                                               version.intValue(), 
+                                               (DistributableSessionTimestamp) data.get(JBossCacheService.TIMESTAMP_KEY), 
+                                               (DistributableSessionMetadata) data.get(JBossCacheService.METADATA_KEY));
+            if (!updated && !isBuddy)
+            {
+               log_.warn("Possible concurrency problem: Replicated version id " + 
+                         version + " matches in-memory version for session " + realId); 
+            }
+            /*else 
+            {
+               We have a local session but got a modification for the buddy tree.
+               This means another node is in the process of taking over the session;
+               we don't worry about it
+            }
+             */
+         }
+         else
+         {
+            log_.warn("No VERSION_KEY attribute found in " + fqn);
+         }
+      }
+      else if (local && !isBuddy
+            && isPossibleInternalPojoFqn(fqn) 
+            && isFqnForOurWebapp(fqn, isBuddy))
+      {
+         // One of our sessions' pojos is modified; need to inform
+         // the manager so it can mark the session dirty
+         String sessId = getIdFromFqn(fqn, isBuddy);
+         manager_.notifyLocalAttributeModification(sessId);
+      }
+   }
+   
+   public static String getPojoKeyFromFqn(Fqn fqn, boolean isBuddy)
+   {
+      return (String) fqn.get(isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + POJO_KEY_FQN_INDEX: POJO_KEY_FQN_INDEX);
+   }
+   
+   /**
+    * Check if the fqn is big enough to be in the internal pojo area but
+    * isn't in the regular attribute area.
+    * 
+    * Structure in the cache is:
+    * 
+    * /JSESSION
+    * ++ /hostname
+    * ++++ /contextpath
+    * ++++++ /sessionid
+    * ++++++++ /ATTRIBUTE
+    * ++++++++ /_JBossInternal_
+    * ++++++++++ etc etc
+    * 
+    * If the Fqn size is big enough to be "etc etc" or lower, but the 4th
+    * level is "ATTRIBUTE", it must be under _JBossInternal_. We discriminate
+    * based on != ATTRIBUTE to avoid having to code to the internal PojoCache
+    * _JBossInternal_ name.
+    * 
+    * @param fqn
+    * @return
+    */
+   public static boolean isPossibleInternalPojoFqn(Fqn fqn)
+   {      
+      return (fqn.size() > POJO_INTERNAL_FQN_SIZE 
+            && JBossCacheService.ATTRIBUTE.equals(fqn.get(POJO_INTERNAL_FQN_INDEX)) == false);
+   }
+}

Added: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/CacheListenerBase.java
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/CacheListenerBase.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/CacheListenerBase.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,105 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.web.tomcat.service.session.distributedcache.impl.jbc;
+
+import org.jboss.cache.Fqn;
+import org.jboss.cache.buddyreplication.BuddyManager;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.LocalDistributableSessionManager;
+
+/**
+ * Base class for JBC cache listener impls.
+ * 
+ * @author Brian Stansberry
+ */
+public class CacheListenerBase
+{
+
+   private static final int JSESSION_FQN_INDEX = 0;
+   private static final int HOSTNAME_FQN_INDEX = 1;
+   private static final int WEBAPP_FQN_INDEX = 2;
+   protected static final int SESSION_ID_FQN_INDEX = 3;
+   private static final int SESSION_FQN_SIZE = SESSION_ID_FQN_INDEX + 1;
+   private static final int BUDDY_BACKUP_ROOT_OWNER_INDEX = BuddyManager.BUDDY_BACKUP_SUBTREE_FQN.size();
+   protected static final int BUDDY_BACKUP_ROOT_OWNER_SIZE = BUDDY_BACKUP_ROOT_OWNER_INDEX + 1;
+   protected LocalDistributableSessionManager manager_;
+   private String webapp_;
+   private String hostname_;
+
+   CacheListenerBase(LocalDistributableSessionManager manager, String hostname, String webapp)
+   {      
+      manager_ = manager;
+      hostname_ = hostname;
+      webapp_ =  webapp;
+   }
+
+   protected boolean isFqnForOurWebapp(Fqn fqn, boolean isBuddy)
+   {
+      try
+      {
+         if (webapp_.equals(fqn.get(isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + WEBAPP_FQN_INDEX : WEBAPP_FQN_INDEX))
+               && hostname_.equals(fqn.get(isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + HOSTNAME_FQN_INDEX : HOSTNAME_FQN_INDEX))
+               && JBossCacheService.SESSION.equals(fqn.get(isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + JSESSION_FQN_INDEX : JSESSION_FQN_INDEX)))
+            return true;
+      }
+      catch (IndexOutOfBoundsException e)
+      {
+         // can't be ours; too small; just fall through
+      }
+   
+      return false;
+   }
+
+   public static boolean isFqnSessionRootSized(Fqn fqn, boolean isBuddy)
+   {
+      return fqn.size() == (isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SESSION_FQN_SIZE : SESSION_FQN_SIZE);
+   }
+
+   public static String getIdFromFqn(Fqn fqn, boolean isBuddy)
+   {
+      return (String)fqn.get(isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SESSION_ID_FQN_INDEX : SESSION_ID_FQN_INDEX);
+   }
+
+   public static boolean isBuddyFqn(Fqn fqn)
+   {
+      try
+      {
+         return BuddyManager.BUDDY_BACKUP_SUBTREE.equals(fqn.get(0));
+      }
+      catch (IndexOutOfBoundsException e)
+      {
+         // Can only happen if fqn is ROOT, and we shouldn't get
+         // notifications for ROOT.
+         // If it does, just means it's not a buddy
+         return false;
+      }      
+   }
+
+   /**
+    * Extracts the owner portion of an buddy subtree Fqn.
+    * 
+    * @param fqn An Fqn that is a child of the buddy backup root node.
+    */
+   public static String getBuddyOwner(Fqn fqn)
+   {
+      return (String) fqn.get(BUDDY_BACKUP_ROOT_OWNER_INDEX);     
+   }
+}
\ No newline at end of file

Added: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/FieldBasedJBossCacheService.java
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/FieldBasedJBossCacheService.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/FieldBasedJBossCacheService.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,279 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.session.distributedcache.impl.jbc;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.jboss.cache.CacheException;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.pojo.PojoCache;
+import org.jboss.ha.framework.server.PojoCacheManager;
+import org.jboss.ha.framework.server.PojoCacheManagerLocator;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.ClusteringNotSupportedException;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSession;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.FieldBasedDistributedCacheManager;
+
+/**
+ * Adds PojoCache-specific capabilities to JBossCacheService.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public class FieldBasedJBossCacheService extends JBossCacheService implements FieldBasedDistributedCacheManager
+{
+   private final PojoCache pojoCache_;
+   
+   public FieldBasedJBossCacheService(String cacheConfigName) throws ClusteringNotSupportedException
+   {
+      this(Util.findPojoCache(cacheConfigName));
+   }
+   
+   /**
+    * Create a new FieldBasedJBossCacheService.
+    * 
+    * @param cache
+    */
+   public FieldBasedJBossCacheService(PojoCache cache)
+   {
+      super(cache.getCache());
+      this.pojoCache_ = cache;
+//      setCache(pojoCache_.getCache());
+      if (!isMarshallingAvailable())
+      {
+         // BES 16/8/2006 -- throw ISE, not ClusteringNotSupportedException, as a
+         // misconfig should be treated differently from the absence of clustering 
+         // services
+         throw new IllegalStateException("replication-granularity value is set to " +
+               "'FIELD' but is not supported by the cache service configuration. " +
+               "Must set 'useRegionBasedMarshalling' to 'true' in the cache configuration");
+      }
+   }
+   
+   @Override
+   protected boolean isFieldBased()
+   {
+      return true;
+   }
+
+   private Fqn getFieldFqn(String id, String key)
+   {
+      return getFieldFqn(hostName_, webAppPath_, id, key);
+   }
+
+
+   private static void breakKeys(String key, List list)
+   {
+      StringTokenizer token = new StringTokenizer(key, FQN_DELIMITER);
+      while(token.hasMoreTokens())
+      {
+         list.add(token.nextToken());
+      }
+   }
+   public static Fqn getFieldFqn(String hostName, String contextPath, String sessionId, String attributeKey)
+   {
+      List list = new ArrayList(6);
+      list.add(SESSION);
+      list.add(hostName);
+      list.add(contextPath);
+      list.add(sessionId);
+      list.add(ATTRIBUTE);
+      // Guard against string with delimiter.
+      breakKeys(attributeKey, list);
+      return new Fqn(list);      
+   }
+
+   /**
+    * store the pojo instance in the cache. Note that this is for the aop cache.
+    * THe pojo needs to be "aspectized".
+    * 
+    * @param realId the session id with any jvmRoute removed
+    * @param key    the attribute key
+    * @param pojo
+    * @param createRegion TODO
+    */
+   public Object setPojo(String realId, String key, Object pojo, DistributableSession session)
+   {
+      if(log_.isTraceEnabled())
+      {
+         log_.trace("setPojo(): session id: " + realId + " key: " + key + 
+                    " object: " + pojo.toString());
+      }      
+      
+      if (session.needRegionForSession())
+      {
+         Fqn sessionRegion = getSessionFqn(realId);
+         getCache().getRegion(sessionRegion, true);
+         session.createdRegionForSession();
+         if (log_.isTraceEnabled())
+         {
+            log_.trace("Created region for session at " + sessionRegion);
+         }         
+      }
+      
+      // Construct the fqn.
+      Fqn fqn = getFieldFqn(realId, key);
+      try {
+         // Ignore any cache notifications that our own work generates 
+//         SessionReplicationContext.startCacheActivity();            
+         return pojoCache_.attach(fqn.toString(), pojo);
+      } catch (CacheException e) {
+         throw new RuntimeException("JBossCacheService: exception occurred in cache setPojo ... ", e);
+      }
+      finally {
+//         SessionReplicationContext.finishCacheActivity();
+      }
+   }
+
+   /**
+    * Remove pojo from the underlying cache store.
+    * @param realId the session id with any jvmRoute removed
+    * @param key    the attribute key
+    * @return pojo that just removed. Null if there none.
+    */
+   public Object removePojo(String realId, String key)
+   {
+      if(log_.isTraceEnabled())
+      {
+         log_.trace("removePojo(): session id: " +realId + " key: " +key);
+      }
+      // Construct the fqn.
+      Fqn fqn = getFieldFqn(realId, key);
+      try {
+         // Ignore any cache notifications that our own work generates 
+//         SessionReplicationContext.startCacheActivity();
+         return pojoCache_.detach(fqn.toString());
+      } catch (CacheException e) {
+         throw new RuntimeException("JBossCacheService: exception occurred in cache removePojo ... ", e);
+      }
+      finally {
+//         SessionReplicationContext.finishCacheActivity();
+      }
+   }
+
+   /**
+    * Remove all the pojos from the underlying cache store locally 
+    * without replication.
+    * 
+    * @param realId the session id with any jvmRoute removed
+    */
+   public void removePojosLocal(String realId)
+   {
+      if(log_.isTraceEnabled())
+      {
+         log_.trace("removePojoLocal(): session id: " +realId);
+      }
+      // Construct the fqn.
+      Fqn fqn = getAttributeFqn(realId);
+      try {
+         // Ignore any cache notifications that our own work generates 
+//         SessionReplicationContext.startCacheActivity();
+         cacheWrapper_.removeLocal(fqn);
+      }
+      finally {
+//         SessionReplicationContext.finishCacheActivity();
+      }
+   }
+
+   /**
+    * Remove all the pojos from the underlying cache store locally 
+    * without replication.
+    * 
+    * @param realId the session id with any jvmRoute removed
+    */
+   public void removePojoLocal(String realId, String key)
+   {
+      if(log_.isTraceEnabled())
+      {
+         log_.trace("removePojoLocal(): session id: " + realId + " key: " +key);
+      }
+      // Construct the fqn.
+      Fqn fqn = getFieldFqn(realId, key);
+      try {
+         // Ignore any cache notifications that our own work generates 
+//         SessionReplicationContext.startCacheActivity();
+         cacheWrapper_.removeLocal(fqn);
+      }
+      finally {
+//         SessionReplicationContext.finishCacheActivity();
+      }
+   }
+   
+   public Set getPojoKeys(String realId)
+   {
+      Set keys = null;
+      Fqn fqn = getAttributeFqn(realId);
+      try
+      {
+         keys = getChildrenNames(fqn);
+      }
+      catch (CacheException e)
+      {
+         log_.error("getPojoKeys(): Exception getting keys for session " + realId, e);
+      }
+      
+      return keys;      
+   }
+   
+
+   /**
+    *
+    * @param realId the session id with any jvmRoute removed
+    * @param key    the attribute key
+    * @return Pojo that is associated with the attribute
+    */
+   public Object getPojo(String realId, String key)
+   {
+      Fqn fqn = getFieldFqn(realId, key);
+      if(log_.isTraceEnabled())
+      {
+         log_.trace("getPojo(): session id: " +realId + " key: " + key +
+                    " fqn: " + fqn);
+      }
+      
+      try 
+      {
+         return pojoCache_.find(fqn);
+      }
+      catch (CacheException e) 
+      {
+         throw new RuntimeException("JBossCacheService: exception occurred in cache find ... ", e);
+      }
+   }
+   
+   protected void releaseCacheToManager(String cacheConfigName)
+   {      
+      try
+      {
+         PojoCacheManager pcm = PojoCacheManagerLocator.getCacheManagerLocator().getCacheManager(null);
+         pcm.releaseCache(cacheConfigName);
+      }
+      catch (Exception e)
+      {
+         log_.error("Problem releasing cache to CacheManager -- config is " + cacheConfigName, e);
+      }
+   }
+
+}

Added: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/JBossCacheService.java
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/JBossCacheService.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/JBossCacheService.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,769 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/
+package org.jboss.web.tomcat.service.session.distributedcache.impl.jbc;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import javax.transaction.TransactionManager;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.CacheException;
+import org.jboss.cache.CacheManager;
+import org.jboss.cache.CacheStatus;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.Node;
+import org.jboss.cache.Region;
+import org.jboss.cache.buddyreplication.BuddyManager;
+import org.jboss.cache.config.BuddyReplicationConfig;
+import org.jboss.cache.config.CacheLoaderConfig;
+import org.jboss.cache.pojo.impl.InternalConstant;
+import org.jboss.cache.transaction.BatchModeTransactionManager;
+import org.jboss.ha.framework.interfaces.CachableMarshalledValue;
+import org.jboss.ha.framework.server.CacheManagerLocator;
+import org.jboss.ha.framework.server.MarshalledValueHelper;
+import org.jboss.ha.framework.server.SimpleCachableMarshalledValue;
+import org.jboss.logging.Logger;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.BatchingManager;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.ClusteringNotSupportedException;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSession;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionMetadata;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionTimestamp;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.LocalDistributableSessionManager;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.SessionSerializationFactory;
+
+/**
+ * A wrapper class to JBossCache. This is currently needed to handle various operations such as
+ * <ul>
+ * <li>Using MarshalledValue to replace Serializable used inside different web app class loader context.</li>
+ * <li>Stripping out any id string after ".". This is to handle the JK failover properly with
+ * Tomcat JvmRoute.</li>
+ * <li>Cache exception retry.</li>
+ * <li>Helper APIS.</li>
+ * </ul>
+ */
+public class JBossCacheService implements DistributedCacheManager
+{   
+   protected static Logger log_ = Logger.getLogger(JBossCacheService.class);
+   public static final String BUDDY_BACKUP = BuddyManager.BUDDY_BACKUP_SUBTREE;
+   public static final Fqn BUDDY_BACKUP_FQN = BuddyManager.BUDDY_BACKUP_SUBTREE_FQN;
+   public static final String SESSION = "JSESSION";
+   public static final String ATTRIBUTE = "ATTRIBUTE";
+   public static final String VERSION_KEY = "V";
+   public static final String TIMESTAMP_KEY = "T";
+   public static final String METADATA_KEY = "M";
+   public static final String ATTRIBUTE_KEY = "A";
+      
+   public static final String FQN_DELIMITER = "/";
+   
+   private Cache plainCache_;
+   
+   /** name of webapp's virtual host; hostName + webAppPath + session id is a unique combo. */
+   protected String hostName_;
+   /** Context path for webapp; hostName + webAppPath + session id is a unique combo. */
+   protected String webAppPath_;
+   protected BatchingManager batchingManager;
+
+   private LocalDistributableSessionManager manager_;
+   private ClassLoader webAppClassLoader_;
+   private CacheListener cacheListener_;
+   protected JBossCacheWrapper cacheWrapper_;
+   
+   /** Do we have to marshall attributes ourself or can we let JBC do it? */
+   private boolean useTreeCacheMarshalling_ = false;
+   
+   /** Are we configured for passivation? */
+   private boolean usePassivation_ = false;
+   private PassivationListener passivationListener_;
+   
+   /** Is cache configured for buddy replication? */
+   private boolean useBuddyReplication_ = false;
+   
+   private String cacheConfigName_;;
+   
+   public JBossCacheService(String cacheConfigName) throws ClusteringNotSupportedException
+   {
+      this(Util.findPlainCache(cacheConfigName));
+      
+      this.cacheConfigName_ = cacheConfigName;
+   }
+   
+   public JBossCacheService(Cache cache)
+   {
+      plainCache_ = cache;
+      
+      cacheWrapper_ = new JBossCacheWrapper(plainCache_);
+      
+      useTreeCacheMarshalling_ = plainCache_.getConfiguration().isUseRegionBasedMarshalling();
+      CacheLoaderConfig clc = plainCache_.getConfiguration().getCacheLoaderConfig();
+      if(clc != null)
+      {
+         usePassivation_ = (clc.isPassivation() && !clc.isShared());
+      }
+   }
+   
+   protected LocalDistributableSessionManager getManager()
+   {
+      return manager_;
+   }
+   
+   protected Cache getCache()
+   {
+      return plainCache_;
+   }
+   
+   protected void setCache(Cache cache)
+   {
+      this.plainCache_ = cache;
+   }
+   
+   protected boolean isFieldBased()
+   {
+      return false;
+   }
+
+   public void start(ClassLoader tcl, LocalDistributableSessionManager manager)
+   {
+      manager_ = manager;
+      webAppClassLoader_ = tcl;
+      
+      String path = manager_.getContextName();
+      if( path.length() == 0 || path.equals("/")) {
+         // If this is root.
+         webAppPath_ = "ROOT";
+      } else if ( path.startsWith("/") ) {
+         webAppPath_ = path.substring(1);
+      } else {
+         webAppPath_ = path;
+      }
+      // JBAS-3941 -- context path can be multi-level, but we don't
+      // want that turning into a multilevel Fqn, so escape it
+      // Use '?' which is illegal in a context path
+      webAppPath_ = webAppPath_.replace('/', '?');
+      log_.debug("Old and new web app path are: " +path + ", " +webAppPath_);
+      
+      String host = manager_.getHostName();
+      if( host == null || host.length() == 0) {
+         hostName_ = "localhost";
+      }else {
+         hostName_ = host;
+      }
+      log_.debug("Old and new virtual host name are: " + host + ", " + hostName_);
+
+      if (plainCache_.getCacheStatus() != CacheStatus.STARTED)
+      {
+         plainCache_.start();
+      }
+
+      // We require the cache batchingManager to be BatchModeTransactionManager now.
+      TransactionManager tm = plainCache_.getConfiguration().getRuntimeConfig().getTransactionManager();
+      if( ! (tm instanceof BatchModeTransactionManager) )
+      {
+         throw new RuntimeException("start(): JBoss Cache transaction manager " +
+                                    "is not of type BatchModeTransactionManager. " +
+                                    "It is " + (tm == null ? "null" : tm.getClass().getName()));
+      }
+      this.batchingManager = new BatchingManagerImpl(tm);
+      
+      Object[] objs = new Object[]{SESSION, hostName_, webAppPath_};
+      Fqn pathFqn = Fqn.fromList(Arrays.asList(objs), true);
+      
+      BuddyReplicationConfig brc = plainCache_.getConfiguration().getBuddyReplicationConfig();
+      this.useBuddyReplication_ = brc != null && brc.isEnabled();
+      if (useTreeCacheMarshalling_ || this.useBuddyReplication_)
+      {
+         // JBAS-5628/JBAS-5629 -- clean out persistent store
+         cleanWebappRegion(pathFqn);
+      }
+
+      // Listen for cache changes
+      cacheListener_ = new CacheListener(cacheWrapper_, manager_, hostName_, webAppPath_, isFieldBased());
+      plainCache_.addCacheListener(cacheListener_);
+      
+      if(useTreeCacheMarshalling_)
+      {
+         // register the tcl and bring over the state for the webapp
+         try
+         {
+            
+            log_.debug("UseMarshalling is true. We will register the fqn: " +
+                        pathFqn + " with class loader" +webAppClassLoader_ +
+                        " and activate the webapp's Region");
+            Node root = plainCache_.getRoot();
+            if (root.hasChild(pathFqn) == false)
+            {
+               plainCache_.getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
+               root.addChild(pathFqn);
+            }
+            Region region = plainCache_.getRegion(pathFqn, true);
+            region.registerContextClassLoader(webAppClassLoader_);
+            region.activate(); 
+         }
+         catch (Exception ex)
+         {
+            throw new RuntimeException("Can't register class loader", ex);
+         }
+      }
+      
+      if(manager_.isPassivationEnabled())
+      {
+         log_.debug("Passivation is enabled");
+         passivationListener_ = new PassivationListener(manager_, hostName_, webAppPath_);
+         plainCache_.addCacheListener(passivationListener_);
+      }
+      else
+      {
+         log_.debug("Passivation is disabled");
+      }
+   }
+
+   private void cleanWebappRegion(Fqn regionFqn)
+   {
+      try {
+         // Remove locally.
+         plainCache_.getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
+         plainCache_.removeNode(regionFqn);
+      }
+      catch (CacheException e)
+      {
+         log_.error("can't clean content from the underlying distributed cache");
+      }
+   }
+
+   public void stop()
+   {
+      plainCache_.removeCacheListener(cacheListener_);      
+      if (passivationListener_ != null)
+         plainCache_.removeCacheListener(passivationListener_);
+      
+      // Construct the fqn
+      Object[] objs = new Object[]{SESSION, hostName_, webAppPath_};
+      Fqn pathFqn = Fqn.fromList(Arrays.asList(objs), true);
+
+      if(useTreeCacheMarshalling_)
+      {
+         log_.debug("UseMarshalling is true. We will inactivate the fqn: " +
+                    pathFqn + " and un-register its classloader");
+            
+         try {
+            Region region = plainCache_.getRegion(pathFqn, false);
+            if (region != null)
+            {
+               region.deactivate();
+               region.unregisterContextClassLoader();
+            }
+         } 
+         catch (Exception e) 
+         {
+            log_.error("Exception during inactivation of webapp region " + pathFqn + 
+                       " or un-registration of its class loader", e);
+         }
+      } 
+      BuddyReplicationConfig brc = plainCache_.getConfiguration().getBuddyReplicationConfig();
+      this.useBuddyReplication_ = brc != null && brc.isEnabled();
+      if (useTreeCacheMarshalling_ || this.useBuddyReplication_)
+      {
+         // JBAS-5628/JBAS-5629 -- clean out persistent store
+         cleanWebappRegion(pathFqn);
+      }
+      // remove session data
+      // BES 2007/08/18 Can't do this as it will 
+      // 1) blow away passivated sessions
+      // 2) leave the cache in an inconsistent state if the war
+      //    is restarted
+//      cacheWrapper_.removeLocalSubtree(pathFqn);
+      
+      if (cacheConfigName_ != null)
+      {
+         releaseCacheToManager(cacheConfigName_);
+      }
+   }
+
+   /**
+    * Get specfically the BatchModeTransactionManager.
+    */
+   public BatchingManager getBatchingManager()
+   {
+      return batchingManager;
+   }
+   
+   /**
+    * Gets whether TreeCache-based marshalling is available
+    */
+   public boolean isMarshallingAvailable()
+   {
+      return useTreeCacheMarshalling_;
+   }
+
+   /**
+    * Loads any serialized data in the cache into the given session
+    * using its <code>readExternal</code> method.
+    *
+    * @return the session passed as <code>toLoad</code>, or
+    *         <code>null</code> if the cache had no data stored
+    *         under the given session id.
+    */
+   public <T extends DistributableSession> T loadSession(String realId, T toLoad)
+   {
+      Fqn fqn = getSessionFqn(realId);
+      Map sessionData =  cacheWrapper_.getData(fqn, true);
+      
+      if (sessionData == null) {
+         // Requested session is no longer in the cache; return null
+         return null;
+      }
+      
+      setupSessionRegion(toLoad, fqn);
+      
+      Integer version = (Integer) sessionData.get(VERSION_KEY);
+      DistributableSessionTimestamp timestamp = (DistributableSessionTimestamp) sessionData.get(TIMESTAMP_KEY);
+      DistributableSessionMetadata metadata = (DistributableSessionMetadata) sessionData.get(METADATA_KEY);
+      Map attrs = (Map) getUnMarshalledValue(sessionData.get(ATTRIBUTE_KEY));
+      toLoad.update(version, timestamp, metadata, attrs);
+      
+      return toLoad;
+   }
+
+   public void putSession(String realId, DistributableSession session)
+   { 
+      if (log_.isTraceEnabled())
+      {
+         log_.trace("putSession(): putting session " + session.getIdInternal());
+      }     
+      
+      Fqn fqn = getSessionFqn(realId);
+      
+      setupSessionRegion(session, fqn);
+      
+      Map map = new HashMap();
+      map.put(VERSION_KEY, new Integer(session.getVersion()));
+      
+      boolean replicateTimestamp = false;
+      
+      if (session.isSessionMetadataDirty())
+      {   
+         map.put(METADATA_KEY, session.getSessionMetadata()); 
+         replicateTimestamp = true;
+      }
+     
+      if (session.isSessionAttributeMapDirty())
+      {
+         Map attrs = session.getSessionAttributeMap();
+         // May be null if the session type doesn't use this mechanism to store
+         // attributes (i.e. isn't SessionBasedClusteredSession)
+         if (attrs != null)
+         {
+            map.put(ATTRIBUTE_KEY, getMarshalledValue(attrs));
+         } 
+         
+         replicateTimestamp = true;
+      }
+      
+      if (replicateTimestamp || session.getMustReplicateTimestamp())
+      {
+         map.put(TIMESTAMP_KEY, session.getSessionTimestamp());
+      }
+      
+      cacheWrapper_.put(fqn, map);
+   }
+
+   /**
+    * If the session requires a region in the cache, establishes one.
+    * 
+    * @param session the session
+    * @param fqn the fqn for the session
+    */
+   private void setupSessionRegion(DistributableSession session, Fqn fqn)
+   {
+      if (session.needRegionForSession())
+      {
+         plainCache_.getRegion(fqn, true);
+         session.createdRegionForSession();
+         if (log_.isTraceEnabled())
+         {
+            log_.trace("Created region for session at " + fqn);
+         }
+      }
+   }
+
+   public void removeSession(String realId, boolean removeRegion)
+   {
+      Fqn fqn = getSessionFqn(realId);
+      if (log_.isTraceEnabled())
+      {
+         log_.trace("Remove session from distributed store. Fqn: " + fqn);
+      }
+      
+      if (removeRegion)
+      {
+         plainCache_.removeRegion(fqn);
+      }
+
+      cacheWrapper_.remove(fqn);
+   }
+
+   public void removeSessionLocal(String realId, boolean removeRegion)
+   {
+      Fqn fqn = getSessionFqn(realId);
+      if (log_.isTraceEnabled())
+      {
+         log_.trace("Remove session from my own distributed store only. Fqn: " + fqn);
+      }
+      
+      if (removeRegion)
+      {
+         plainCache_.removeRegion(fqn);
+      }
+      
+      cacheWrapper_.removeLocal(fqn);
+   }
+
+   public void removeSessionLocal(String realId, String dataOwner)
+   {
+      if (dataOwner == null)
+      {
+         removeSessionLocal(realId, false);
+      }
+      else
+      {         
+         Fqn fqn = getSessionFqn(realId, dataOwner);
+         if (log_.isTraceEnabled())
+         {
+            log_.trace("Remove session from my own distributed store only. Fqn: " + fqn);
+         }
+         cacheWrapper_.removeLocal(fqn);
+      }
+   }   
+      
+   public void evictSession(String realId)
+   {
+      evictSession(realId, null);      
+   }   
+      
+   public void evictSession(String realId, String dataOwner)
+   {    
+      Fqn fqn = dataOwner == null ? getSessionFqn(realId) : getSessionFqn(realId, dataOwner);
+      if(log_.isTraceEnabled())
+      {
+         log_.trace("evictSession(): evicting session from my distributed store. Fqn: " + fqn);
+      }
+      cacheWrapper_.evictSubtree(fqn);      
+   }
+   
+   public Map getSessionData(String realId, String dataOwner)
+   {
+      Fqn fqn = dataOwner == null ? getSessionFqn(realId) : getSessionFqn(realId, dataOwner);
+      return cacheWrapper_.getData(fqn, false);
+   }
+
+   public Object getAttribute(String realId, String key)
+   {
+      Fqn fqn = getAttributeFqn(realId);
+      return getUnMarshalledValue(cacheWrapper_.get(fqn, key));
+   }
+
+   public void putAttribute(String realId, String key, Object value)
+   {
+      Fqn fqn = getAttributeFqn(realId);
+      cacheWrapper_.put(fqn, key, getMarshalledValue(value));
+   }
+
+   public void putAttribute(String realId, Map map)
+   {
+      // Duplicate the map with marshalled values
+      Map marshalled = new HashMap(map.size());
+      Set entries = map.entrySet();
+      for (Iterator it = entries.iterator(); it.hasNext(); )
+      {
+         Map.Entry entry = (Map.Entry) it.next();
+         marshalled.put(entry.getKey(), getMarshalledValue(entry.getValue()));
+      }
+      
+      Fqn fqn = getAttributeFqn(realId);
+      cacheWrapper_.put(fqn, marshalled);
+      
+   }
+
+   public void removeAttributes(String realId)
+   {
+      Fqn fqn = getAttributeFqn(realId);
+      cacheWrapper_.remove(fqn);
+   }
+
+   public Object removeAttribute(String realId, String key)
+   {
+      Fqn fqn = getAttributeFqn(realId);
+      if (log_.isTraceEnabled())
+      {
+         log_.trace("Remove attribute from distributed store. Fqn: " + fqn + " key: " + key);
+      }
+      return getUnMarshalledValue(cacheWrapper_.remove(fqn, key));
+   }
+
+   public void removeAttributesLocal(String realId)
+   {
+      Fqn fqn = getAttributeFqn(realId);
+      if (log_.isTraceEnabled())
+      {
+         log_.trace("Remove attributes from my own distributed store only. Fqn: " + fqn);
+      }
+      cacheWrapper_.removeLocal(fqn);
+   }
+
+   /**
+    * Obtain the keys associated with this fqn. Note that it is not the fqn children.
+    *
+    */
+   public Set getAttributeKeys(String realId)
+   {
+      Set keys = null;
+      Fqn fqn = getAttributeFqn(realId);
+      try
+      {
+         Node node = plainCache_.getRoot().getChild(fqn);
+         if (node != null)
+            keys = node.getKeys();
+      }
+      catch (CacheException e)
+      {
+         log_.error("getAttributeKeys(): Exception getting keys for session " + realId, e);
+      }
+      
+      return keys;
+   }
+
+   /**
+    * Return all attributes associated with this session id.
+    * 
+    * @param realId the session id with any jvmRoute removed
+    * @return the attributes, or any empty Map if none are found.
+    */
+   public Map getAttributes(String realId)
+   {
+      if (realId == null || realId.length() == 0) return new HashMap();
+      
+      Map attrs = new HashMap();
+      Fqn fqn = getAttributeFqn(realId);
+      
+      Node node = plainCache_.getRoot().getChild(fqn);
+      Map rawData = node.getData();
+      
+      for (Iterator it = rawData.entrySet().iterator(); it.hasNext();)
+      {
+         Entry entry = (Entry) it.next();
+         attrs.put(entry.getKey(), getUnMarshalledValue(entry.getValue()));
+      }
+      
+      return attrs;
+   }
+
+   /**
+    * Gets the ids of all sessions in the underlying cache.
+    *
+    * @return Map<String, String> containing all of the session ids of sessions in the cache
+    *         (with any jvmRoute removed) as keys, and the identifier of the data owner for
+    *         the session as value (or a <code>null</code>  value if buddy
+    *         replication is not enabled.) Will not return <code>null</code>.
+    */
+   public Map<String, String> getSessionIds()
+   {
+      Map<String, String> result = new HashMap<String, String>();
+      
+      Node bbRoot = plainCache_.getRoot().getChild(BUDDY_BACKUP_FQN);
+      if (bbRoot != null)
+      {
+         Set owners = bbRoot.getChildren();
+         if (owners != null)
+         {
+            for (Iterator it = owners.iterator(); it.hasNext();)
+            {
+               Node owner = (Node) it.next();
+               Node webRoot = owner.getChild(getWebappFqn());
+               if (webRoot != null)
+               {
+                  Set<String> ids = webRoot.getChildrenNames();
+                  storeSessionOwners(ids, (String) owner.getFqn().getLastElement(), result);
+               }
+            }
+         }
+      }
+      
+      storeSessionOwners(getChildrenNames(getWebappFqn()), null, result);
+
+      return result;
+   }
+   
+   protected Set getChildrenNames(Fqn fqn)
+   {
+      Node node = plainCache_.getRoot().getChild(fqn);
+      return (node == null ? null : node.getChildrenNames());
+   }
+
+   private void storeSessionOwners(Set<String> ids, String owner, Map<String, String> map)
+   {
+      if (ids != null)
+      {
+         for (String id : ids)
+         {            
+            if (!InternalConstant.JBOSS_INTERNAL_STRING.equals(id))
+            {
+               map.put(id, owner);
+            }
+         }
+      }
+   }
+ 
+   public boolean isPassivationEnabled()
+   {
+      return usePassivation_;      
+   }
+
+   protected Fqn getWebappFqn()
+   {
+      // /SESSION/hostname/webAppPath
+      Object[] objs = new Object[]{SESSION, hostName_, webAppPath_};
+      return Fqn.fromList(Arrays.asList(objs), true);
+   }
+   
+   protected Fqn getSessionFqn(String id)
+   {
+      return getSessionFqn(hostName_, webAppPath_, id);
+   }
+   
+   public static Fqn getSessionFqn(String hostname, String contextPath, String sessionId)
+   {
+      Object[] objs = new Object[]{SESSION, hostname, contextPath, sessionId};
+      return Fqn.fromList(Arrays.asList(objs), true);
+   }
+
+   private Fqn getSessionFqn(String id, String dataOwner)
+   {
+      return getSessionFqn(dataOwner, hostName_, webAppPath_, id);
+   }
+   
+   public static Fqn getSessionFqn(String dataOwner, String hostname, String contextPath, String sessionId)
+   {
+      Object[] objs = new Object[]{BUDDY_BACKUP, dataOwner, SESSION, hostname, contextPath, sessionId};
+      return Fqn.fromList(Arrays.asList(objs), true);
+   }
+
+   protected Fqn getAttributeFqn(String id)
+   {
+      return getAttributeFqn(hostName_, webAppPath_, id);
+   }
+
+   public static Fqn getAttributeFqn(String hostname, String contextPath, String sessionId)
+   {
+      Object[] objs = new Object[]{SESSION, hostname, contextPath, sessionId, ATTRIBUTE};
+      return Fqn.fromList(Arrays.asList(objs), true);
+   }
+   
+   protected void releaseCacheToManager(String cacheConfigName)
+   {      
+      try
+      {
+         CacheManager cm = CacheManagerLocator.getCacheManagerLocator().getCacheManager(null);
+         cm.releaseCache(cacheConfigName);
+      }
+      catch (Exception e)
+      {
+         log_.error("Problem releasing cache to CacheManager -- config is " + cacheConfigName, e);
+      }
+   }
+
+   private Object getMarshalledValue(Object value)
+   {
+      // JBAS-2920.  For now, continue using MarshalledValue, as 
+      // it allows lazy deserialization of the attribute on remote nodes
+      // For Branch_4_0 this is what we have to do anyway for backwards
+      // compatibility. For HEAD we'll follow suit for now.
+      // TODO consider only using MV for complex objects (i.e. not primitives)
+      // and Strings longer than X.
+      
+//      if (useTreeCacheMarshalling_)
+//      {
+//         return value;
+//      }
+//      else
+//      {
+         
+         // JBAS-2921 - replaced MarshalledValue calls with SessionSerializationFactory calls
+         // to allow for switching between JBossSerialization and JavaSerialization using
+         // system property -D=session.serialization.jboss=true / false
+         // MarshalledValue mv = new MarshalledValue(value);
+         if  (MarshalledValueHelper.isTypeExcluded(value.getClass()))
+         {
+            return value;               
+         }
+         else 
+         {
+            try
+            {
+               CachableMarshalledValue mv = SessionSerializationFactory.createMarshalledValue((Serializable) value);
+               return mv;
+            }
+            catch (ClassCastException e)
+            {
+               throw new IllegalArgumentException(value + " does not implement java.io.Serializable");
+            }
+         }
+//      }
+   }
+
+   private Object getUnMarshalledValue(Object obj)
+   {
+      if (!(obj instanceof SimpleCachableMarshalledValue))
+            return obj;
+         
+      // Swap in/out the tcl for this web app. Needed only for un marshalling.
+      ClassLoader prevTCL = Thread.currentThread().getContextClassLoader();
+      Thread.currentThread().setContextClassLoader(webAppClassLoader_);
+      try
+      {
+         SimpleCachableMarshalledValue mv = (SimpleCachableMarshalledValue) obj;
+         mv.setObjectStreamSource(SessionSerializationFactory.getObjectStreamSource());
+         return mv.get();
+      }
+      catch (IOException e)
+      {
+         log_.error("IOException occurred unmarshalling value ", e);
+         return null;
+      }
+      catch (ClassNotFoundException e)
+      {
+         log_.error("ClassNotFoundException occurred unmarshalling value ", e);
+         return null;
+      }
+      finally
+      {
+         Thread.currentThread().setContextClassLoader(prevTCL);
+      }
+   }
+
+}

Added: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/JBossCacheWrapper.java
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/JBossCacheWrapper.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/JBossCacheWrapper.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,255 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/
+package org.jboss.web.tomcat.service.session.distributedcache.impl.jbc;
+
+import java.util.Map;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.lock.TimeoutException;
+
+public class JBossCacheWrapper 
+{
+   private static final int RETRY = 3;
+   private static final String RETRY_FAIL_MSG = 
+      "Continued to catch TimeoutException during " + 
+       RETRY + " retry attempts. Giving up.";
+   private Cache plainCache_;
+   
+   JBossCacheWrapper(Cache cache)
+   {
+      plainCache_ = cache;
+   }
+   
+   Map getData(Fqn fqn, boolean gravitate)
+   {
+      TimeoutException ex = null;
+      for (int i = 0; i < RETRY; i++)
+      {
+         try
+         {            
+            if (gravitate)
+            {            
+               plainCache_.getInvocationContext().getOptionOverrides()
+                                                 .setForceDataGravitation(true);
+            }
+            return plainCache_.getData(fqn);
+         }
+         catch (TimeoutException e)
+         {
+            ex = e;
+         }
+      }      
+      throw new RuntimeException(RETRY_FAIL_MSG, ex);      
+   }
+
+   /**
+    * Wrapper to embed retry logic.
+    *
+    * @param fqn
+    * @param id
+    * @return
+    */
+   Object get(Fqn fqn, String id)
+   {
+      return get(fqn, id, false);
+   }
+
+   /**
+    * Wrapper to embed retry logic.
+    *
+    * @param fqn
+    * @param id
+    * @return
+    */
+   Object get(Fqn fqn, String id, boolean gravitate)
+   {
+      TimeoutException ex = null;
+      for (int i = 0; i < RETRY; i++)
+      {
+         try
+         {            
+            if (gravitate)
+            {            
+               plainCache_.getInvocationContext().getOptionOverrides()
+                                                 .setForceDataGravitation(true);
+            }
+            return plainCache_.get(fqn, id);
+         }
+         catch (TimeoutException e)
+         {
+            ex = e;
+         }
+      }
+      
+      throw new RuntimeException(RETRY_FAIL_MSG, ex);
+   }
+
+   /**
+    * Wrapper to embed retry logic.
+    *
+    * @param fqn
+    * @param id
+    * @param value
+    * @return
+    */
+   void put(Fqn fqn, String id, Object value)
+   {
+      TimeoutException ex = null;
+      for (int i = 0; i < RETRY; i++)
+      {
+         try
+         {
+            plainCache_.put(fqn, id, value);
+            return;
+         }
+         catch (TimeoutException e)
+         {
+            ex = e;
+         }
+      }
+      
+      throw new RuntimeException(RETRY_FAIL_MSG, ex);
+   }
+
+
+   /**
+    * Wrapper to embed retry logic.
+    *
+    * @param fqn
+    * @param map
+    */
+   void put(Fqn fqn, Map map)
+   {
+      TimeoutException ex = null;
+      for (int i = 0; i < RETRY; i++)
+      {
+         try
+         {
+            plainCache_.put(fqn, map);
+            return;
+         }
+         catch (TimeoutException e)
+         {
+            ex = e;
+         }
+      }
+      
+      throw new RuntimeException(RETRY_FAIL_MSG, ex);
+   }
+
+   /**
+    * Wrapper to embed retry logic.
+    *
+    * @param fqn
+    * @param id
+    * @return
+    */
+   Object remove(Fqn fqn, String id)
+   {
+      TimeoutException ex = null;
+      for (int i = 0; i < RETRY; i++)
+      {
+         try
+         {
+            return plainCache_.remove(fqn, id);
+         }
+         catch (TimeoutException e)
+         {
+            ex = e;
+         }
+      }
+      
+      throw new RuntimeException(RETRY_FAIL_MSG, ex);
+   }
+
+   /**
+    * Wrapper to embed retry logic.
+    *
+    * @param fqn
+    */
+   void remove(Fqn fqn)
+   {
+      TimeoutException ex = null;
+      for (int i = 0; i < RETRY; i++)
+      {
+         try
+         {
+            plainCache_.removeNode(fqn);
+            return;
+         }
+         catch (TimeoutException e)
+         {
+            ex = e;
+         }
+      }
+      
+      throw new RuntimeException(RETRY_FAIL_MSG, ex);
+   }
+   
+   /**
+    * Wrapper to embed retry logic.
+    *
+    * @param fqn
+    */
+   void removeLocal(Fqn fqn)
+   {
+      TimeoutException ex = null;
+      for (int i = 0; i < RETRY; i++)
+      {
+         try
+         {
+            plainCache_.getInvocationContext().getOptionOverrides()
+                                              .setCacheModeLocal(true);
+            plainCache_.removeNode(fqn);
+            return;
+         }
+         catch (TimeoutException e)
+         {
+            ex = e;
+         }
+      }
+      
+      throw new RuntimeException(RETRY_FAIL_MSG, ex);
+   }
+   
+   void evictSubtree(Fqn fqn)
+   {      
+      TimeoutException ex = null;
+      for (int i = 0; i < RETRY; i++)
+      {
+         try
+         {
+            plainCache_.evict(fqn, true);
+            return;
+            
+         }
+         catch (TimeoutException e)
+         {
+            ex = e;
+         }
+      }
+      
+      throw new RuntimeException(RETRY_FAIL_MSG, ex);
+   }
+
+}

Added: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/PassivationListener.java
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/PassivationListener.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/PassivationListener.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,76 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.session.distributedcache.impl.jbc;
+
+import org.jboss.cache.Fqn;
+import org.jboss.cache.notifications.annotation.NodeActivated;
+import org.jboss.cache.notifications.event.NodeActivatedEvent;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.LocalDistributableSessionManager;
+
+/**
+ * Listener for JBoss Cache activation events.  Triggers updates of
+ * the passivation counter.
+ * 
+ * @author <a href="brian.stansberry at jboss.com">Brian Stansberry</a>
+ * @version $Revision: 64678 $
+ */
+ at org.jboss.cache.notifications.annotation.CacheListener
+public class PassivationListener extends CacheListenerBase
+{
+
+   PassivationListener(LocalDistributableSessionManager manager, String hostname, String webapp)
+   {      
+      super(manager, hostname, webapp);
+   }
+   
+   // NOTE: Don't track passivation from here -- we know in JBossCacheManager
+   // when we trigger a passivation. Avoid spurious listener callbacks to
+   // webapps that aren't interested.
+   
+//   @NodePassivated
+//   public void nodePassivated(NodePassivatedEvent event)
+//   {
+//      Fqn fqn = event.getFqn();
+//      if (isFqnForOurWebapp(fqn, isBuddyFqn(fqn)))
+//      {
+//         manager_.sessionPassivated();
+//      }
+//   }
+   
+   // We do want activation callbacks, as JBossCacheManager can't readily
+   // track whether a cache read is going to result in an activation
+   
+   @NodeActivated
+   public void nodeActivated(NodeActivatedEvent event)
+   {
+      Fqn fqn = event.getFqn();
+      boolean isBuddy = isBuddyFqn(fqn);      
+      if (isFqnSessionRootSized(fqn, isBuddy) 
+            && isFqnForOurWebapp(fqn, isBuddy))
+      {
+         manager_.sessionActivated();
+      }
+      
+   }
+
+}

Added: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/Util.java
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/Util.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/session/distributedcache/impl/jbc/Util.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,121 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2006, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/
+
+package org.jboss.web.tomcat.service.session.distributedcache.impl.jbc;
+
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.CacheManager;
+import org.jboss.cache.pojo.PojoCache;
+import org.jboss.ha.framework.server.CacheManagerLocator;
+import org.jboss.ha.framework.server.PojoCacheManager;
+import org.jboss.ha.framework.server.PojoCacheManagerLocator;
+import org.jboss.web.tomcat.service.session.distributedcache.spi.ClusteringNotSupportedException;
+
+/**
+ * Utility methods related to JBoss distributed sessions.
+ * 
+ * @author Brian Stansberry
+ * @version $Revision: 56542 $
+ */
+public class Util
+{
+   public static PojoCache findPojoCache(String cacheConfigName) throws ClusteringNotSupportedException
+   {
+      PojoCacheManager pcm = getManagerForPojoCache(cacheConfigName);
+      
+      try
+      {
+         return pcm.getPojoCache(cacheConfigName, true);
+      }
+      catch (RuntimeException re)
+      {
+         throw re;
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException("Problem accessing cache " + cacheConfigName, e);
+      }
+   }
+   
+   public static Cache findPlainCache(String cacheConfigName) throws ClusteringNotSupportedException
+   {
+      CacheManager pcm = getManagerForCache(cacheConfigName);
+      
+      try
+      {
+         return pcm.getCache(cacheConfigName, true);
+      }
+      catch (RuntimeException re)
+      {
+         throw re;
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException("Problem accessing cache " + cacheConfigName, e);
+      }
+   }
+   
+   private static PojoCacheManager getManagerForPojoCache(String cacheConfigName) 
+      throws ClusteringNotSupportedException
+   {
+      PojoCacheManager pcm = null;
+      try
+      {
+         pcm = PojoCacheManagerLocator.getCacheManagerLocator().getCacheManager(null);
+      }
+      catch (Throwable t)
+      {
+         throw new ClusteringNotSupportedException("Could not access PojoCacheManager for JBossWeb clustering", t);
+      }
+      
+      if (!pcm.getConfigurationNames().contains(cacheConfigName))
+         throw new IllegalStateException("PojoCacheManager does not recognize config " + cacheConfigName);
+      
+      return pcm;
+   }
+   
+   private static CacheManager getManagerForCache(String cacheConfigName) 
+      throws ClusteringNotSupportedException
+   {
+      CacheManager cm = null;
+      try
+      {
+         cm = CacheManagerLocator.getCacheManagerLocator().getCacheManager(null);
+      }
+      catch (Throwable t)
+      {
+         throw new ClusteringNotSupportedException("Could not access CacheManager for JBossWeb clustering", t);
+      }
+      
+      if (!cm.getConfigurationNames().contains(cacheConfigName))
+         throw new IllegalStateException("CacheManager does not recognize config " + cacheConfigName);
+      
+      return cm;
+   }
+   
+   /**
+    * Prevent instantiation.
+    */
+   private Util() {}
+
+}

Added: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/sso/jbc/JBossCacheSSOClusterManager.java
===================================================================
--- projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/sso/jbc/JBossCacheSSOClusterManager.java	                        (rev 0)
+++ projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/sso/jbc/JBossCacheSSOClusterManager.java	2008-08-30 02:55:19 UTC (rev 77673)
@@ -0,0 +1,1501 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2006, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/
+package org.jboss.web.tomcat.service.sso.jbc;
+
+import java.io.Serializable;
+import java.security.AccessController;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.transaction.Status;
+import javax.transaction.TransactionManager;
+
+import org.jboss.cache.Cache;
+import org.jboss.cache.CacheManager;
+import org.jboss.cache.CacheStatus;
+import org.jboss.cache.Fqn;
+import org.jboss.cache.InvocationContext;
+import org.jboss.cache.Node;
+import org.jboss.cache.Region;
+import org.jboss.cache.RegionNotEmptyException;
+import org.jboss.cache.config.Option;
+import org.jboss.cache.config.Configuration.CacheMode;
+import org.jboss.cache.notifications.annotation.CacheListener;
+import org.jboss.cache.notifications.annotation.NodeModified;
+import org.jboss.cache.notifications.annotation.NodeRemoved;
+import org.jboss.cache.notifications.annotation.ViewChanged;
+import org.jboss.cache.notifications.event.NodeModifiedEvent;
+import org.jboss.cache.notifications.event.NodeRemovedEvent;
+import org.jboss.cache.notifications.event.ViewChangedEvent;
+import org.jboss.cache.pojo.PojoCache;
+import org.jboss.ha.framework.server.CacheManagerLocator;
+import org.jboss.logging.Logger;
+import org.jboss.util.NestedRuntimeException;
+import org.jboss.util.loading.ContextClassLoaderSwitcher;
+import org.jboss.util.threadpool.ThreadPool;
+import org.jboss.web.tomcat.service.sso.spi.FullyQualifiedSessionId;
+import org.jboss.web.tomcat.service.sso.spi.SSOClusterManager;
+import org.jboss.web.tomcat.service.sso.spi.SSOCredentials;
+import org.jboss.web.tomcat.service.sso.spi.SSOLocalManager;
+
+/**
+ * An implementation of SSOClusterManager that uses a TreeCache
+ * to share SSO information between cluster nodes.
+ *
+ * @author Brian E. Stansberry
+ * @version $Revision: 59567 $ $Date: 2007-01-12 03:39:24 +0100 (ven., 12 janv. 2007) $
+ */
+ at CacheListener
+public final class JBossCacheSSOClusterManager
+   implements SSOClusterManager
+{
+   // -------------------------------------------------------------  Constants
+
+   /**
+    * Final segment of any FQN that names a TreeCache node storing
+    * SSO credential information.
+    */
+   private static final String CREDENTIALS = "credentials";
+
+   /**
+    * First segment of any FQN that names a TreeCache node associated
+    * with an SSO
+    */
+   private static final String SSO = "SSO";
+
+   /**
+    * Final segment of any FQN that names a TreeCache node storing
+    * the set of Sessions associated with an SSO.
+    */
+   private static final String SESSIONS = "sessions";
+
+   /**
+    * Key under which data is stored to the TreeCache.
+    */
+   private static final String KEY = "key";
+
+   /**
+    * Default global value for the threadPoolName property
+    */
+   public static final String DEFAULT_THREAD_POOL_NAME =
+      "jboss.system:service=ThreadPool";
+   
+   /** The default JBoss Cache to use for storing SSO entries */
+   public static final String DEFAULT_CACHE_NAME = "clustered-sso";
+   
+   /** The legacy name of the JBoss Cache to use for storing SSO entries */
+   public static final String LEGACY_CACHE_NAME = "jboss.cache:service=TomcatClusteringCache";
+   
+   // -------------------------------------------------------  Instance Fields
+   
+   /**
+    * SSO id which the thread is currently storing to the cache
+    */
+   private ThreadLocal<String> beingLocallyAdded = new ThreadLocal<String>();
+
+   /**
+    * SSO id which a thread is currently removing from the cache
+    */
+   private ThreadLocal<String> beingLocallyRemoved = new ThreadLocal<String>();
+
+   /**
+    * SSO id which the thread is deregistering due to removal on another node
+    */
+   private ThreadLocal<String> beingRemotelyRemoved = new ThreadLocal<String>();
+
+   /**
+    * String name to use to access the TreeCache
+    */
+   private String cacheName = null;
+
+   /**
+    * ObjectName of the TreeCache if legacy JMX integration is used
+    */
+   private ObjectName cacheObjectName = null;
+   
+   /**
+    * The cache itself.
+    */
+   private Cache cache = null;
+
+   /**
+    * Transaction Manager
+    */
+   private TransactionManager tm = null;
+   
+   private String threadPoolName = DEFAULT_THREAD_POOL_NAME;
+
+   private ThreadPool threadPool;
+
+   /**
+    * The Log-object for this class
+    */
+   private Logger log = Logger.getLogger(getClass().getName());;
+
+   /**
+    * Whether we are registered as a TreeCacheListener anywhere
+    */
+   private boolean registeredAsListener = false;
+
+   /**
+    * The MBean server we use to access our TreeCache
+    */
+   private MBeanServer server = null;
+
+   /**
+    * The SingleSignOn for which we are providing cluster support
+    */
+   private SSOLocalManager ssoValve = null;
+
+   /**
+    * Whether we have been started
+    */
+   private boolean started = false;
+
+   /**
+    * Whether a valid TreeCache is available for use
+    */
+   private boolean treeCacheAvailable = false;
+
+   /**
+    * Whether we have logged an error due to not having a valid cache
+    */
+   private boolean missingCacheErrorLogged = false;
+   
+   /**
+    * Our node's address in the cluster.
+    */
+   private Serializable localAddress = null;
+   
+   /** The members of the last view passed to viewChange() */
+   private Set currentView = new HashSet();;
+   
+   /** Mutex lock to ensure only one view change at a time is being processed */
+   private Object cleanupMutex = new Object();
+   
+   // ----------------------------------------------------------  Constructors
+
+   
+   /**
+    * Creates a new JBossCacheSSOClusterManager
+    */
+   public JBossCacheSSOClusterManager()  {}
+
+   
+   /**
+    * Creates a new JBossCacheSSOClusterManager that works with the given
+    * MBeanServer.  This constructor is only intended for use in unit testing.
+    */
+   public JBossCacheSSOClusterManager(MBeanServer server)
+   {
+      this.server = server;
+   }
+   
+   
+   // ------------------------------------------------------------  Properties
+  
+   public String getCacheName()
+   {
+      return cacheName;
+   }
+
+   public void setCacheName(String configName)
+      throws Exception
+   {
+      this.cacheName = configName;
+   }
+   
+   public String getThreadPoolName()
+   {
+      return threadPoolName;
+   }
+
+
+   public void setThreadPoolName(String threadPoolName)
+   {
+      if (started)
+      {
+         log.info("Call to setThreadPoolName() ignored; already started");
+      }
+      else
+      {
+         this.threadPoolName = threadPoolName;
+      }
+   }
+   
+   public boolean isUsingThreadPool()
+   {
+      return threadPool != null;
+   }
+   
+   // -----------------------------------------------------  SSOClusterManager
+
+   /**
+    * Notify the cluster of the addition of a Session to an SSO session.
+    *
+    * @param ssoId   the id of the SSO session
+    * @param sessionId id of the Session that has been added
+    */
+   public void addSession(String ssoId, FullyQualifiedSessionId sessionId)
+   {
+      if (ssoId == null || sessionId == null)
+      {
+         return;
+      }
+
+      if (!checkTreeCacheAvailable())
+      {
+         return;
+      }
+
+      if (log.isTraceEnabled())
+      {
+         log.trace("addSession(): adding Session " + sessionId.getSessionId() +
+            " to cached session set for SSO " + ssoId);
+      }
+
+      Fqn fqn = getSessionsFqn(ssoId);
+      boolean doTx = false;
+      try
+      {
+         // Confirm we have a transaction manager; if not get it from TreeCache
+         // failure to find will throw an IllegalStateException
+         if (tm == null)
+            configureFromCache();
+         
+         // Don't do anything if there is already a transaction 
+         // context associated with this thread.
+         if(tm.getTransaction() == null)
+            doTx = true;
+
+         if(doTx)
+            tm.begin();
+         
+         putInTreeCache(fqn, sessionId, null);
+      }
+      catch (Exception e)
+      {
+         try
+         {
+            if(doTx)
+               tm.setRollbackOnly();
+         }
+         catch (Exception ignored)
+         {
+         }
+         log.error("caught exception adding session " + sessionId.getSessionId() +
+            " to SSO id " + ssoId, e);
+      }
+      finally
+      {
+         if (doTx)
+            endTransaction();
+      }
+   }
+
+
+   /**
+    * Gets the SingleSignOn valve for which this object is handling
+    * cluster communications.
+    *
+    * @return the <code>SingleSignOn</code> valve.
+    */
+   public SSOLocalManager getSSOLocalManager()
+   {
+      return ssoValve;
+   }
+
+
+   /**
+    * Sets the SingleSignOn valve for which this object is handling
+    * cluster communications.
+    * <p><b>NOTE:</b> This method must be called before calls can be
+    * made to the other methods of this interface.
+    *
+    * @param localManager a <code>SingleSignOn</code> valve.
+    */
+   public void setSSOLocalManager(SSOLocalManager localManager)
+   {
+      ssoValve = localManager;
+      if (ssoValve != null)
+      {
+         if (server == null)
+         {
+            server = ssoValve.getMBeanServer();
+         }
+         String config = ssoValve.getCacheConfig();
+         if (config != null)
+         {
+            cacheName = config;
+         }
+      }
+   }
+
+
+   /**
+    * Notifies the cluster that a single sign on session has been terminated
+    * due to a user logout.
+    *
+    * @param ssoId
+    */
+   public void logout(String ssoId)
+   {
+      if (!checkTreeCacheAvailable())
+      {
+         return;
+      }
+      
+      // Check whether we are already handling this removal 
+      if (ssoId.equals(beingLocallyRemoved.get()))
+      {
+         return;
+      }         
+      
+      // Add this SSO to our list of in-process local removals so
+      // this.nodeRemoved() will ignore the removal
+      beingLocallyRemoved.set(ssoId);
+
+      if (log.isTraceEnabled())
+      {
+         log.trace("Registering logout of SSO " + ssoId +
+            " in clustered cache");
+      }
+
+      Fqn fqn = getSingleSignOnFqn(ssoId);
+      
+      try
+      {         
+         removeFromTreeCache(fqn, false);
+      }
+      catch (Exception e)
+      {
+         log.error("Exception attempting to remove node " +
+            fqn.toString() + " from TreeCache", e);
+      }
+      finally
+      {
+         beingLocallyRemoved.set(null);
+      }
+   }
+
+
+   public SSOCredentials lookup(String ssoId)
+   {
+      if (!checkTreeCacheAvailable())
+      {
+         return null;
+      }
+
+      SSOCredentials credentials = null;
+      // Find the latest credential info from the cluster
+      Fqn fqn = getCredentialsFqn(ssoId);
+      try
+      {         
+         credentials = (SSOCredentials) getFromTreeCache(fqn, KEY);
+      }
+      catch (Exception e)
+      {
+         log.error("caught exception looking up SSOCredentials for SSO id " +
+            ssoId, e);
+      }
+      return credentials;
+   }
+
+
+   /**
+    * Notifies the cluster of the creation of a new SSO entry.
+    *
+    * @param ssoId    the id of the SSO session
+    * @param authType the type of authenticator (BASIC, CLIENT-CERT, DIGEST
+    *                 or FORM) used to authenticate the SSO.
+    * @param username the username (if any) used for the authentication
+    * @param password the password (if any) used for the authentication
+    */
+   public void register(String ssoId, String authType,
+      String username, String password)
+   {
+      if (!checkTreeCacheAvailable())
+      {
+         return;
+      }
+
+      if (log.isTraceEnabled())
+      {
+         log.trace("Registering SSO " + ssoId + " in clustered cache");
+      }
+      
+      storeSSOData(ssoId, authType, username, password);
+   }
+
+
+   /**
+    * Notify the cluster of the removal of a Session from an SSO session.
+    *
+    * @param ssoId   the id of the SSO session
+    * @param sessionId id of the Session that has been removed
+    */
+   public void removeSession(String ssoId, FullyQualifiedSessionId sessionId)
+   {
+      if (ssoId == null || sessionId == null)
+      {
+         return;
+      }
+      
+      if (!checkTreeCacheAvailable())
+      {
+         return;
+      }
+      
+      // Check that this session removal is not due to our own deregistration
+      // of an SSO following receipt of a nodeRemoved() call
+      if (ssoId.equals(beingRemotelyRemoved.get()))
+      {
+         return;
+      }
+
+      if (log.isTraceEnabled())
+      {
+         log.trace("removeSession(): removing Session " + sessionId.getSessionId() +
+            " from cached session set for SSO " + ssoId);
+      }
+
+      Fqn fqn = getSessionsFqn(ssoId);
+      boolean doTx = false;
+      boolean removing = false;
+      try
+      {
+         // Confirm we have a transaction manager; if not get it from TreeCache
+         // failure to find will throw an IllegalStateException
+         if (tm == null)
+            configureFromCache();
+
+         // Don't do anything if there is already a transaction 
+         // context associated with this thread.
+         if(tm.getTransaction() == null)
+            doTx = true;
+
+         if(doTx)
+            tm.begin();
+
+         Set keys = getSessionKeys(ssoId);
+         if (keys.contains(sessionId))
+         {
+            if (keys.size() == 1)
+            {
+               // This is our last session locally; remove our node (which,
+               // via nodeRemoved callback also marks the sso empty if it's 
+               // also the last session globally                  
+               removeFromTreeCache(fqn, false);                
+            }
+            else
+            {
+               // Simple removal of one our local sessions
+               removeFromTreeCache(fqn, sessionId);
+            }
+         }
+      }
+      catch (Exception e)
+      {
+         try
+         {
+            if(doTx)
+               tm.setRollbackOnly();
+         }
+         catch (Exception x)
+         {
+         }
+         
+         log.error("caught exception removing session " + sessionId.getSessionId() +
+            " from SSO id " + ssoId, e);
+      }
+      finally
+      {
+         try
+         {
+            if (removing)
+            {
+               beingLocallyRemoved.set(null);
+            }
+         }
+         finally
+         {
+            if (doTx)
+               endTransaction();
+         }
+      }
+   }
+
+
+   /**
+    * Notifies the cluster of an update of the security credentials
+    * associated with an SSO session.
+    *
+    * @param ssoId    the id of the SSO session
+    * @param authType the type of authenticator (BASIC, CLIENT-CERT, DIGEST
+    *                 or FORM) used to authenticate the SSO.
+    * @param username the username (if any) used for the authentication
+    * @param password the password (if any) used for the authentication
+    */
+   public void updateCredentials(String ssoId, String authType,
+      String username, String password)
+   {
+      if (!checkTreeCacheAvailable())
+      {
+         return;
+      }
+
+      if (log.isTraceEnabled())
+      {
+         log.trace("Updating credentials for SSO " + ssoId +
+            " in clustered cache");
+      }
+
+      storeSSOData(ssoId, authType, username, password);
+   }
+
+   
+   // ------------------------------------------------------  CacheListener
+
+   /**
+    * Extracts an SSO session id from the Fqn and uses it in an invocation of
+    * {@link ClusteredSingleSignOn#deregister(String) ClusteredSingleSignOn.deregister(String)}.
+    * <p/>
+    * Ignores invocations resulting from TreeCache changes originated by
+    * this object.
+    *
+    * @param fqn the fully-qualified name of the node that was removed
+    */
+   @NodeRemoved
+   public void nodeRemoved(NodeRemovedEvent event)
+   {
+      if (event.isPre())
+         return;
+      
+      Fqn fqn = event.getFqn();
+      String ssoId = getIdFromFqn(fqn);
+      
+      if (ssoId == null)
+         return;
+      
+      if (fqn.size() == 2)
+      {
+         // Entire SSO is being removed; i.e. an invalidation
+         
+         // Ignore messages generated by our own logout activity
+         if (!ssoId.equals(beingLocallyRemoved.get()))
+         {
+            handleRemoteInvalidation(ssoId);
+         }
+      }
+      else if (fqn.size() == 4)
+      {
+         // A peer is gone
+         handlePeerRemoval(ssoId);
+      }
+   }
+
+   /**
+    * If any nodes have been removed from the view, asynchronously scans
+    * all SSOs looking for and removing sessions owned by the removed node.
+    * Notifies the SSO valve if as a result any SSOs no longer have active
+    * sessions.  If the removed node is the one associated with this object,
+    * does nothing.
+    */
+   @ViewChanged
+   public synchronized void viewChange(ViewChangedEvent event)
+   {
+      if (event.isPre())
+         return;
+      
+      log.debug("Received ViewChangedEvent " + event);
+      
+      Set oldMembers = new HashSet(currentView);   
+      synchronized (currentView)
+      {
+         currentView.clear();
+         currentView.addAll(event.getNewView().getMembers());
+         
+         // If we're not in the view, just exit
+         if (localAddress == null || !currentView.contains(localAddress))
+            return;
+         
+         // Remove all the current members from the old set; any left 
+         // are the dead members
+         oldMembers.removeAll(currentView);
+      }
+      
+      if (oldMembers.size() > 0)
+      {
+         log.debug("Members have been removed; will launch cleanup task. Dead members: " + oldMembers);
+         
+         launchSSOCleaner(false);
+      }
+         
+   }
+
+
+   /**
+    * Instantiates a DeadMemberCleaner and assigns a thread
+    * to execute the cleanup task.
+    * @param notifyIfEmpty TODO
+    */
+   private void launchSSOCleaner(boolean notifyIfEmpty)
+   {
+      SSOCleanerTask cleaner = new SSOCleanerTask();
+      cleaner.setCheckForEmpty(notifyIfEmpty);
+      if (threadPool != null)
+      {
+         threadPool.run(cleaner);
+      }
+      else
+      {
+         Thread t = new Thread(cleaner, "ClusteredSSOCleaner");
+         t.setDaemon(true);
+         t.start();
+      }
+   }   
+
+
+   /**
+    * Handles the notification that an entire SSO has been removed remotely
+    * 
+    * @param ssoId id of the removed SSO
+    */
+   private void handleRemoteInvalidation(String ssoId)
+   {
+      beingRemotelyRemoved.set(ssoId);
+
+      try
+      {
+         if (log.isTraceEnabled())
+         {
+            log.trace("received a node removed message for SSO " + ssoId);
+         }
+
+         ssoValve.deregister(ssoId);
+      }
+      finally
+      {
+         beingRemotelyRemoved.set(null);
+      }
+   }
+   
+   /**
+    * Checks whether any peers remain for the given SSO; if not
+    * notifies the valve that the SSO is empty.
+    * 
+    * @param ssoId
+    */
+   private void handlePeerRemoval(String ssoId)
+   {
+      try
+      {
+         Set peers = getSSOPeers(ssoId);
+         if (peers.size() == 0)
+         {
+            ssoValve.notifySSOEmpty(ssoId);
+         }
+      }
+      catch (Exception e)
+      {
+         log.error("Caught exception checking if " +  ssoId + " is empty", e);
+      }
+   }
+
+   /**
+    * Extracts an SSO session id from the Fqn and uses it in an invocation of
+    * {@link ClusteredSingleSignOn#update ClusteredSingleSignOn.update()}.
+    * <p/>
+    * Only responds to modifications of nodes whose FQN's final segment is
+    * "credentials".
+    * <p/>
+    * Ignores invocations resulting from TreeCache changes originated by
+    * this object.
+    * <p/>
+    * Ignores invocations for SSO session id's that are not registered
+    * with the local SingleSignOn valve.
+    *
+    * @param fqn the fully-qualified name of the node that was modified
+    */
+   @NodeModified
+   public void nodeModified(NodeModifiedEvent event)
+   {
+      if (event.isPre() || event.isOriginLocal())
+         return;
+      
+      Fqn fqn = event.getFqn();
+      String type = getTypeFromFqn(fqn);
+      if (CREDENTIALS.equals(type))
+      {
+         handleCredentialUpdate(getIdFromFqn(fqn), event.getData());
+      }
+      else if (SESSIONS.equals(type))
+      {
+         handleSessionSetChange(fqn);
+      }
+   }
+
+   /**  
+    * @param ssoId the id of the sso
+    * @param nodeData JBC data map assoicated with the update
+    */
+   private void handleCredentialUpdate(String ssoId, Map nodeData)
+   {
+      // Ignore invocations that come as a result of our additions
+      if (ssoId.equals(beingLocallyAdded.get()))
+      {
+         return;
+      }
+
+      if (log.isTraceEnabled())
+      {
+         log.trace("received a credentials modified message for SSO " + ssoId);
+      }
+
+      try
+      {
+         SSOCredentials data = (SSOCredentials) nodeData.get(KEY);
+         if (data != null)
+         {
+            ssoValve.remoteUpdate(ssoId, data);
+         }
+      }
+      catch (Exception e)
+      {
+         log.error("failed to update credentials for SSO " + ssoId, e);
+      }
+   }
+   
+   /**
+    *  
+    * @param fqn an Fqn that points to the SESSIONS node of an SSO or lower
+    */
+   private void handleSessionSetChange(Fqn fqn)
+   {
+      // Ignore anything not for a peer's session node
+      if (fqn.size() != 4)
+         return;
+
+      // Peers remove their entire node when it's empty, so any
+      // other modification means it's not empty      
+      ssoValve.notifySSONotEmpty(getIdFromFqn(fqn));
+   }
+
+   /**
+    * Prepare for the beginning of active use of the public methods of this
+    * component.  This method should be called before any of the public
+    * methods of this component are utilized.  It should also send a
+    * LifecycleEvent of type START_EVENT to any registered listeners.
+    *
+    * @throws LifecycleException if this component detects a fatal error
+    *                            that prevents this component from being used
+    */
+   public void start() throws Exception
+   {
+      // Validate and update our current component state
+      if (started)
+      {
+         throw new IllegalStateException("JBossCacheSSOClusterManager already Started");
+      }
+      
+      initThreadPool();
+
+      if (isCacheAvailable(true))
+      {
+         integrateWithCache();
+      }
+      
+      started = true;
+   }
+
+
+   /**
+    * Gracefully terminate the active use of the public methods of this
+    * component.  This method should be the last one called on a given
+    * instance of this component.  It should also send a LifecycleEvent
+    * of type STOP_EVENT to any registered listeners.
+    *
+    * @throws LifecycleException if this component detects a fatal error
+    *                            that needs to be reported
+    */
+   public void stop() throws Exception
+   {
+      // Validate and update our current component state
+      if (!started)
+      {
+         throw new IllegalStateException("JBossCacheSSOClusterManager not Started");
+      }
+      
+      started = false;
+   }
+
+   
+   // -------------------------------------------------------  Public Methods
+
+   /**
+    * Gets the number of sessions associated with the given SSO. The same
+    * session active on more than one node will count twice.
+    */
+   public int getSessionCount(String ssoId) throws Exception
+   {
+      int count = 0;
+      Set peers = getSSOPeers(ssoId);
+      for (Iterator it = peers.iterator(); it.hasNext();)
+      {
+         Set ids = getSessionKeys(ssoId, (Serializable) it.next());
+         count += ids.size();
+      }
+      return count;
+   }
+   
+   // -------------------------------------------------------  Private Methods
+
+   private Object getFromTreeCache(Fqn fqn, Object key) throws Exception
+   {
+      return cache.get(fqn, key);
+   }
+   
+   private Set getSSOIds() throws Exception
+   {
+      Fqn ssoRootFqn = Fqn.fromElements(new Object[] {SSO});
+      Node ssoRoot = cache.getRoot().getChild(ssoRootFqn);
+      return ssoRoot == null ? new HashSet() : ssoRoot.getChildrenNames();
+   }
+   
+   private Set getSSOPeers(String ssoId) throws Exception
+   {
+      Fqn fqn = getSessionRootFqn(ssoId);
+      Node ssoRoot = cache.getRoot().getChild(fqn);
+      return ssoRoot == null ? new HashSet() : ssoRoot.getChildrenNames();
+   }
+
+   private Fqn getCredentialsFqn(String ssoid)
+   {
+      Object[] objs = new Object[]{SSO, ssoid, CREDENTIALS};
+      return Fqn.fromElements(objs);
+   }
+
+   private Fqn getSessionRootFqn(String ssoId)
+   {
+      Object[] objs = new Object[]{SSO, ssoId, SESSIONS };
+      return Fqn.fromElements(objs);      
+   }
+   
+   private Fqn getSessionsFqn(String ssoid)
+   {
+      return getSessionsFqn(ssoid, localAddress);
+   }
+   
+   private Fqn getSessionsFqn(String ssoid, Serializable address)
+   {
+      Object[] objs = new Object[]{SSO, ssoid, SESSIONS, address };
+      return Fqn.fromElements(objs);
+   }
+
+   private Fqn getSingleSignOnFqn(String ssoid)
+   {
+      Object[] objs = new Object[]{SSO, ssoid};
+      return Fqn.fromElements(objs);
+   }
+
+   /**
+    * Extracts an SSO session id from a fully qualified name object.
+    *
+    * @param fqn the Fully Qualified Name used by TreeCache
+    * @return the second element in the Fqn -- the SSO session id
+    */
+   private String getIdFromFqn(Fqn fqn)
+   {
+      String id = null;
+      if (fqn.size() > 1 && SSO.equals(fqn.get(0)))
+      {
+         id = (String) fqn.get(1);
+      }
+      return id;
+   }
+
+   /**
+    * Extracts the SSO tree cache node type from a fully qualified name
+    * object.
+    *
+    * @param fqn the Fully Qualified Name used by TreeCache
+    * @return the 3rd in the Fqn -- either
+    *         {@link #CREDENTIALS CREDENTIALS} or {@link #SESSIONS SESSIONS},
+    *         or <code>null</code> if <code>fqn</code> is not for an SSO.
+    */
+   private String getTypeFromFqn(Fqn fqn)
+   {
+      String type = null;
+      if (fqn.size() > 2 && SSO.equals(fqn.get(0)))
+         type = (String) fqn.get(2);
+      return type;
+   }
+   
+   private Set getSessionKeys(String ssoId)
+   {
+      return getSessionKeys(ssoId, localAddress);
+   }
+   
+   private Set getSessionKeys(String ssoId, Serializable peer)
+   {
+      Fqn fqn = getSessionsFqn(ssoId, peer);
+      Set keys = null;
+      Node sessions = cache.getRoot().getChild(fqn);
+      if (sessions != null)
+      {
+         keys = sessions.getKeys();
+      }
+      else
+      {
+          keys = new HashSet();
+      }
+      return keys;
+   }
+
+   /**
+    * Obtains needed configuration information from the tree cache.
+    * Invokes "getTransactionManager" on the tree cache, caching the
+    * result or throwing an IllegalStateException if one is not found.
+    * Confirms that the cache is not configured for buddy replication,
+    * throwing IllegalStateException if it is. Also gets our cluster-wide 
+    * unique local address from the cache.
+    * 
+    * @throws Exception
+    */
+   private void configureFromCache() throws Exception
+   {  
+      tm = cache.getConfiguration().getRuntimeConfig().getTransactionManager();
+
+      if (tm == null) 
+      {
+         throw new IllegalStateException("Cache does not have a " +
+                                         "transaction manager; please " +
+                                         "configure a valid " +
+                                         "TransactionManagerLookupClass");
+      }
+      
+      if (cache.getConfiguration().getBuddyReplicationConfig() != null
+            && cache.getConfiguration().getBuddyReplicationConfig().isEnabled())
+      {
+         throw new IllegalStateException("Underlying cache is configured for " +
+                                         "buddy replication; use of buddy " +
+                                         "replication with ClusteredSingleSignOn " +
+                                         "is not supported");
+      }
+      
+      // Find out our address
+      Object address = cache.getLocalAddress();
+      // In reality this is a JGroups IpAddress, but the API says
+      // "Object" so we have to be sure its Serializable
+      if (address instanceof Serializable)
+         localAddress = (Serializable) address;
+      else if (address != null)
+         localAddress = address.toString();
+      else if (CacheMode.LOCAL == cache.getConfiguration().getCacheMode())
+         localAddress = "LOCAL";
+      else 
+         throw new IllegalStateException("Cannot get local address from cache");
+      
+      
+      log.debug("Local address is " + localAddress);
+      
+      // Get the currentView
+      synchronized (currentView)
+      {
+         currentView.clear();
+         List members = cache.getMembers();
+         if (members != null)
+         {
+            currentView.addAll(members);
+            
+            log.debug("Current view is " + currentView);
+         }
+      }
+   }   
+
+   private void endTransaction()
+   {
+      try 
+      {
+         if(tm.getTransaction().getStatus() != Status.STATUS_MARKED_ROLLBACK)
+         {
+            tm.commit();
+         } 
+         else
+         {
+            tm.rollback();
+         }
+      } 
+      catch (Exception e) 
+      {
+         log.error(e);
+         throw new NestedRuntimeException("JBossCacheSSOClusterManager.endTransaction(): ", e);
+      }
+   }
+   
+   private MBeanServer getMBeanServer()
+   {
+      if (server == null && ssoValve != null)
+      {
+         server = ssoValve.getMBeanServer();
+      }
+      return server;
+   }
+
+   /**
+    * Checks whether an MBean is registered under the value of property
+    * "cacheObjectName".
+    *
+    * @param forceCheck check for availability whether or not it has already
+    *                   been positively established
+    * @return <code>true</code> if property <code>cacheName</code> has been
+    *         set and points to a registered MBean.
+    */
+   private synchronized boolean isCacheAvailable(boolean forceCheck)
+   {
+      if (forceCheck || treeCacheAvailable == false)
+      {
+         boolean available = (cacheName != null);
+         if (available)
+         {       
+            try
+            {
+               CacheManager cm = CacheManagerLocator.getCacheManagerLocator().getCacheManager(null);
+               available = cm.getConfigurationNames().contains(cacheName);
+            }
+            catch (IllegalStateException ise)
+            {
+               log.debug("No CacheManager available");
+               available = false;
+            }
+            
+            if (!available && getMBeanServer() != null && cacheName.indexOf(':') > -1)
+            {
+               // See if there is a legacy JMX binding
+               String onameStr = cacheName;
+               if (DEFAULT_CACHE_NAME.equals(cacheName))
+                  onameStr = LEGACY_CACHE_NAME;
+               try
+               {
+                  ObjectName oname = new ObjectName(onameStr);
+                  Set s = getMBeanServer().queryMBeans(oname, null);
+                  if (s.size() > 0)
+                  {
+                     available = true;
+                     // Save the object name to tell integrateWithCache to use JMX
+                     cacheObjectName = oname;
+                     cacheName = onameStr;
+                  }
+               }
+               catch (Exception e)
+               {
+                  // no jmx
+               }
+            }
+            
+            if (available)
+            {
+               try
+               {
+                  // If Tomcat6 overrides the default cache name, it will do so
+                  // after we are started. So we need to configure ourself here
+                  // and throw an exception if there is a problem. Having this
+                  // here also allows us to recover if our cache is started
+                  // after we are
+                  if (started)
+                     integrateWithCache();
+                  setMissingCacheErrorLogged(false);
+               }
+               catch (Exception e)
+               {
+                  log.error("Caught exception configuring from cache " +
+                            cacheName, e);
+                  available = false;
+               }
+            }
+         }
+         treeCacheAvailable = available;
+      }
+      return treeCacheAvailable;
+   }
+   
+   private boolean checkTreeCacheAvailable()
+   {
+      boolean avail = isCacheAvailable(false);
+      if (!avail)
+         logMissingCacheError();
+      return avail;
+   }
+
+   private void putInTreeCache(Fqn fqn, Object key, Object data) throws Exception
+   {
+      cache.put(fqn, key, data);
+   }
+
+   private void integrateWithCache() throws Exception
+   {
+      if (cache == null)
+      {
+         // 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
+         {
+            // Determine if our cache is a PojoCache or a plain Cache
+            if (cacheObjectName == null)
+            {
+               CacheManager cm = CacheManagerLocator.getCacheManagerLocator().getCacheManager(null);
+               cache = cm.getCache(cacheName, true);               
+            }
+            else if (server != null)
+            {            
+               // Look in JMX
+               MBeanInfo info = server.getMBeanInfo(cacheObjectName);
+               MBeanAttributeInfo[] attrs = info.getAttributes();
+               for (MBeanAttributeInfo attr : attrs)
+               {
+                  if ("PojoCache".equals(attr.getName()))
+                  {
+                     cache = ((PojoCache) getMBeanServer().getAttribute(cacheObjectName, "PojoCache")).getCache();
+                     break;
+                  }
+                  else if ("Cache".equals(attr.getName()))
+                  {
+                     cache = (Cache) getMBeanServer().getAttribute(cacheObjectName, "Cache");
+                     break;
+                  }
+               }
+            }
+            else
+            {
+               // Shouldn't be possible or isTreeCacheAvailable would return false
+               throw new IllegalStateException("No JBoss Cache available under name " + cacheName);
+            }
+            
+            if (cache.getCacheStatus() != CacheStatus.STARTED)
+               cache.start();
+         }
+         finally
+         {
+            // Restore the TCCL
+            switchContext.reset();
+         }
+         
+         // Ensure we have a transaction manager and a cluster-wide unique address
+         configureFromCache();
+         
+         // If the SSO region is inactive, activate it
+         activateCacheRegion();
+         
+         registerAsCacheListener();
+         
+         // Scan for any SSOs with no entries; mark them for expiration
+         launchSSOCleaner(true);
+         
+         log.debug("Successfully integrated with cache service " + cacheName);
+      }
+   }
+
+
+   /**
+    * If we are sharing a cache with HttpSession replication, the SSO
+    * region may not be active, so here we ensure it is.
+    * 
+    * @throws Exception
+    */   
+   private void activateCacheRegion() throws Exception
+   {
+      if (cache.getConfiguration().isInactiveOnStartup())
+      {
+         if (cache.getConfiguration().isUseRegionBasedMarshalling())
+         {
+            Region region =cache.getRegion(Fqn.fromString("/" + SSO), true);
+            try
+            {
+               region.activate();
+            }
+            catch (RegionNotEmptyException e)
+            {
+               log.debug(SSO + " region already active", e);
+            }
+         }
+      }
+   }
+
+   /**
+    * Invokes an operation on the JMX server to register ourself as a
+    * listener on the TreeCache service.
+    *
+    * @throws Exception
+    */
+   private void registerAsCacheListener() throws Exception
+   {
+      cache.addCacheListener(this);
+      registeredAsListener = true;
+   }
+
+
+   /**
+    * Invokes an operation on the JMX server to register ourself as a
+    * listener on the TreeCache service.
+    *
+    * @throws Exception
+    */
+   private void removeAsCacheListener() throws Exception
+   {
+      if (registeredAsListener && cache != null)
+      {
+         cache.removeCacheListener(this);
+         registeredAsListener = false;
+      }
+   }
+
+   private void removeFromTreeCache(Fqn fqn, boolean localOnly) throws Exception
+   {
+      if (localOnly)
+      {
+         InvocationContext ctx = cache.getInvocationContext();
+         Option option = new Option();
+         option.setCacheModeLocal(true);
+         ctx.setOptionOverrides(option);
+      }
+      cache.removeNode(fqn);
+   }
+
+   private void removeFromTreeCache(Fqn fqn, Object key) throws Exception
+   {
+      cache.remove(fqn, key);
+   }
+
+   /**
+    * Stores the given data to the clustered cache in a tree branch whose FQN
+    * is the given SSO id.  Stores the given credential data in a child node
+    * named "credentials".  If parameter <code>storeSessions</code> is
+    * <code>true</code>, also stores an empty HashSet in a sibling node
+    * named "sessions".  This HashSet will later be used to hold session ids
+    * associated with the SSO.
+    * <p/>
+    * Any items stored are stored under the key "key".
+    *
+    * @param ssoId    the id of the SSO session
+    * @param authType the type of authenticator (BASIC, CLIENT-CERT, DIGEST
+    *                 or FORM) used to authenticate the SSO.
+    * @param username the username (if any) used for the authentication
+    * @param password the password (if any) used for the authentication
+    */
+   private void storeSSOData(String ssoId, String authType, String username,
+      String password)
+   {
+      SSOCredentials data = new SSOCredentials(authType, username, password);
+      
+      // Add this SSO to our list of in-process local adds so
+      // this.nodeModified() will ignore the addition
+      beingLocallyAdded.set(ssoId);
+      
+      try
+      {
+         putInTreeCache(getCredentialsFqn(ssoId), KEY, data);
+      }
+      catch (Exception e)
+      {
+         log.error("Exception attempting to add TreeCache nodes for SSO " +
+            ssoId, e);
+      }
+      finally
+      {
+         beingLocallyAdded.set(null);
+      }
+   }
+   
+   private void initThreadPool()
+   {      
+      if (threadPoolName != null && getMBeanServer() != null)
+      {
+         try
+         {
+            ObjectName on = new ObjectName(threadPoolName);
+            threadPool = (ThreadPool) server.getAttribute(on, "Instance");
+            log.debug("Using ThreadPool at " + threadPoolName + " to clean dead members");
+         }
+         catch (Exception e)
+         {
+            log.info("Unable to access ThreadPool at " + threadPoolName + 
+                     " -- will use individual threads for cleanup work");
+            log.debug("Failure to access ThreadPool due to: " + e);
+         }
+      }
+      else
+      {
+         log.debug("No ThreadPool configured -- will use individual threads for cleanup work");         
+      }
+   }
+
+   private boolean isMissingCacheErrorLogged()
+   {
+      return missingCacheErrorLogged;
+   }
+
+   private void setMissingCacheErrorLogged(boolean missingCacheErrorLogged)
+   {
+      this.missingCacheErrorLogged = missingCacheErrorLogged;
+   }
+
+   private void logMissingCacheError()
+   {
+      StringBuffer msg = new StringBuffer("Cannot find TreeCache using ");
+      msg.append(getCacheName());
+      msg.append(" -- TreeCache must be started before ClusteredSingleSignOn ");
+      msg.append("can handle requests");
+
+      if (isMissingCacheErrorLogged())
+      {
+         // Just log it as a warning
+         log.warn(msg);
+      }
+      else
+      {
+         log.error(msg);
+         // Set a flag so we don't relog this error over and over
+         setMissingCacheErrorLogged(true);
+      }
+   }
+
+   // ---------------------------------------------------------  Outer Classes
+
+   /**
+    * Runnable that's run when the removal of a node from the cluster has been detected. 
+    * Removes any SessionAddress objects associated with dead members from the
+    * session set of each SSO.  Operates locally only so each node can independently clean
+    * its SSOs without concern about replication lock conflicts.
+    */
+   private class SSOCleanerTask implements Runnable
+   {    
+      boolean checkForEmpty = false;
+      
+      
+      boolean getCheckForEmpty()
+      {
+         return checkForEmpty;
+      }
+
+      void setCheckForEmpty(boolean checkForEmpty)
+      {
+         this.checkForEmpty = checkForEmpty;
+      }
+
+      public void run()
+      {
+         synchronized (cleanupMutex)
+         {
+            try
+            {      
+               // Ensure we have a TransactionManager
+               if (tm == null)
+                  configureFromCache();
+               
+               Set ids = getSSOIds();
+               for (Iterator iter = ids.iterator(); iter.hasNext();)
+               {
+                  cleanSSO((String) iter.next());                  
+               }
+            }
+            catch (Exception e)
+            {
+               log.error("Caught exception cleaning sessions from dead cluster members from SSOs ", e);
+            }
+         }
+      }
+      
+      private void cleanSSO(String ssoId)
+      {
+         boolean doTx = false;
+         try
+         {
+            // Don't start tx if there is already one associated with this thread.
+            if(tm.getTransaction() == null)
+               doTx = true;
+
+            if(doTx)
+               tm.begin();
+            
+            Set peers = getSSOPeers(ssoId);
+            if (peers != null && peers.size() > 0)
+            {
+               for (Iterator iter = peers.iterator(); iter.hasNext(); )
+               {
+                  Serializable peer = (Serializable) iter.next();
+                  boolean alive = true;
+                  synchronized (currentView)
+                  {
+                     alive = currentView.contains(peer);
+                  }
+                  if (!alive)
+                  {
+                     if (log.isTraceEnabled())
+                     {
+                        log.trace("Removing peer " + peer + " from SSO " + ssoId);
+                     }
+                     
+                     Fqn fqn = getSessionsFqn(ssoId, peer);
+                     // Remove the peer node, but local-only
+                     // Each cache is responsible for cleaning itself
+                     removeFromTreeCache(fqn, true);
+                  }
+               }
+            }
+            else if (checkForEmpty)
+            {
+               // SSO has no peers; notify our valve so we can expire it
+               ssoValve.notifySSOEmpty(ssoId);
+            }
+         }
+         catch (Exception e)
+         {
+            try
+            {
+               if(doTx)
+                  tm.setRollbackOnly();
+            }
+            catch (Exception ignored)
+            {
+            }
+            log.error("caught exception cleaning dead members from SSO " + ssoId, e);
+         }
+         finally
+         {
+            if (doTx)
+               endTransaction();
+         }         
+      }
+   }
+
+} // end JBossCacheSSOClusterManager
+


Property changes on: projects/cluster/ha-server-cache-jbc/trunk/src/main/java/org/jboss/web/tomcat/service/sso/jbc/JBossCacheSSOClusterManager.java
___________________________________________________________________
Name: svn:executable
   + *




More information about the jboss-cvs-commits mailing list