[jboss-cvs] JBossAS SVN: r58021 - in branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5: session sso

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Thu Nov 2 15:46:27 EST 2006


Author: bstansberry at jboss.com
Date: 2006-11-02 15:46:25 -0500 (Thu, 02 Nov 2006)
New Revision: 58021

Modified:
   branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/session/JBossManager.java
   branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/ClusteredSingleSignOn.java
   branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/TreeCacheSSOClusterManager.java
   branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/mbeans-descriptors.xml
Log:
[JBAS-3808] Prevent clustered SSO invalidation on webapp undeploy or server shutdown

Modified: branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/session/JBossManager.java
===================================================================
--- branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/session/JBossManager.java	2006-11-02 20:44:41 UTC (rev 58020)
+++ branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/session/JBossManager.java	2006-11-02 20:46:25 UTC (rev 58021)
@@ -208,6 +208,15 @@
       return sessionIDGenerator_.getSessionId();
    }
 
+   /**
+    * Gets the JMX <code>ObjectName</code> under
+    * which our <code>TreeCache</code> is registered. 
+    */
+   public ObjectName getObjectName()
+   {
+      return objectName_;
+   }
+
    public boolean isUseLocalCache()
    {
       return useLocalCache_;

Modified: branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/ClusteredSingleSignOn.java
===================================================================
--- branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/ClusteredSingleSignOn.java	2006-11-02 20:44:41 UTC (rev 58020)
+++ branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/ClusteredSingleSignOn.java	2006-11-02 20:46:25 UTC (rev 58021)
@@ -8,22 +8,36 @@
 
 
 import org.jboss.web.tomcat.tc5.Tomcat5;
+import org.jboss.web.tomcat.tc5.session.JBossManager;
 
 import java.io.IOException;
 import java.security.Principal;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
 
+import javax.management.ObjectName;
 import javax.servlet.ServletException;
 import javax.servlet.http.Cookie;
 
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
 import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Manager;
 import org.apache.catalina.Session;
 import org.apache.catalina.Realm;
 import org.apache.catalina.SessionEvent;
 import org.apache.catalina.authenticator.Constants;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
+import org.apache.catalina.session.ManagerBase;
 
+import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
 
+
 /**
  * A <strong>Valve</strong> that supports a "single sign on" user experience,
  * where the security identity of a user who successfully authenticates to one
@@ -46,8 +60,13 @@
  */
 public class ClusteredSingleSignOn
    extends org.apache.catalina.authenticator.SingleSignOn
+   implements LifecycleListener
 {
-
+   /** By default we process expired SSOs no more often than once per minute */
+   public static final int DEFAULT_PROCESS_EXPIRES_INTERVAL = 60;
+   /** By default we let SSOs without active sessions live for 30 mins */
+   public static final int DEFAULT_MAX_EMPTY_LIFE = 1800;
+   
    // Override the superclass value
    static
    {
@@ -77,6 +96,30 @@
     */
    private String treeCacheName = Tomcat5.DEFAULT_CACHE_NAME;
 
+   /** Currently started Managers that have associated as session with an SSO */
+   private Set activeManagers = Collections.synchronizedSet(new HashSet());
+   
+   /** Max number of ms an SSO with no active sessions will be usable by a request */
+   private int maxEmptyLife = DEFAULT_MAX_EMPTY_LIFE * 1000;
+   
+   /** 
+    * Minimum number of ms since the last processExpires() run 
+    * before a new run is allowed.
+    */
+   private int processExpiresInterval = DEFAULT_PROCESS_EXPIRES_INTERVAL * 1000;
+   
+   /** Timestamp of the last processExpires() run */
+   private long lastProcessExpires = System.currentTimeMillis();
+   
+   /** 
+    * Map<String, Long> containing the ids of SSOs with no active sessions
+    * and the time at which they entered that state
+    */
+   private Map emptySSOs = new ConcurrentHashMap();
+   
+   /** Used for sync locking of processExpires runs */
+   private final Object mutex = new Object();
+
    // ------------------------------------------------------------- Properties
 
    /**
@@ -195,7 +238,105 @@
       }
    }
 
+   /**
+    * Gets the max number of seconds an SSO with no active sessions will be 
+    * usable by a request.
+    * 
+    * @return a non-negative number
+    * 
+    * @see #DEFAULT_MAX_EMPTY_LIFE    * 
+    * @see #setMaxEmptyLife() 
+    */
+   public int getMaxEmptyLife()
+   {
+      return (maxEmptyLife / 1000);
+   }
 
+
+   /**
+    * Sets the maximum number of seconds an SSO with no active sessions will be 
+    * usable by a request.
+    * <p>
+    * A positive value for this property allows a user to continue to use an SSO
+    * even after all the sessions associated with it have been expired. It does not
+    * keep an SSO alive if a session associated with it has been invalidated due to 
+    * an <code>HttpSession.invalidate()</code> call.
+    * </p>
+    * <p>
+    * The primary purpose of this property is to avoid the situation where a server
+    * on which all of an SSO's sessions lives is shutdown, thus expiring all the
+    * sessions and causing the invalidation of the SSO. A positive value for this
+    * property would give the user an opportunity to fail over to another server
+    * and maintain the SSO.
+    * </p>
+    * 
+    * @param maxEmptyLife a non-negative number
+    * 
+    * @throws IllegalArgumentException if <code>maxEmptyLife < 0</code>
+    */
+   public void setMaxEmptyLife(int maxEmptyLife)
+   {
+      if (maxEmptyLife < 0)
+         throw new IllegalArgumentException("maxEmptyLife must be >= 0");
+      
+      this.maxEmptyLife = maxEmptyLife * 1000;
+   }
+
+
+   /**
+    * Gets the minimum number of seconds since the start of the last check for overaged
+    * SSO's with no active sessions before a new run is allowed.
+    * 
+    * @return a positive number
+    * 
+    * @see #DEFAULT_PROCESS_EXPIRES_INTERVAL
+    * @see #setMaxEmptyLife() 
+    * @see #setProcessExpiresInterval(int)
+    */
+   public int getProcessExpiresInterval()
+   {
+      return processExpiresInterval / 1000;
+   }
+
+   /**
+    * Sets the minimum number of seconds since the start of the last check for overaged
+    * SSO's with no active sessions before a new run is allowed.  During this check,
+    * any such overaged SSOs will be invalidated. 
+    * <p>
+    * Note that setting this value does not imply that a check will be performed
+    * every <code>processExpiresInterval</code> seconds, only that it will not
+    * be performed more often than that.
+    * </p>
+    * 
+    * @param processExpiresInterval a non-negative number. <code>0</code> means
+    *                               the overage check can be performed whenever
+    *                               the container wishes to.
+    *                               
+    * @throws IllegalArgumentException if <code>processExpiresInterval < 1</code>
+    * 
+    * @see #setMaxEmptyLife()
+    */
+   public void setProcessExpiresInterval(int processExpiresInterval)
+   {
+      if (processExpiresInterval < 0)
+         throw new IllegalArgumentException("processExpiresInterval must be >= 0");
+      
+      this.processExpiresInterval = processExpiresInterval * 1000;
+   }
+
+
+   /**
+    * Gets the timestamp of the start of the last check for overaged
+    * SSO's with no active sessions.
+    * 
+    * @see #setProcessExpiresInterval(int)
+    */
+   public long getLastProcessExpires()
+   {
+      return lastProcessExpires;
+   }
+
+
    // ------------------------------------------------------ Lifecycle Methods
 
 
@@ -292,25 +433,112 @@
       if (ssoId == null)
          return;
 
-      // Was the session destroyed as the result of a timeout?
-      // If so, we'll just remove the expired session from the
-      // SSO.  If the session was logged out, we'll log out
-      // of all sessions associated with the SSO.
-      if ((session.getMaxInactiveInterval() > 0)
-         && (System.currentTimeMillis() - session.getLastAccessedTime() >=
-         session.getMaxInactiveInterval() * 1000))
+      try
       {
-         removeSession(ssoId, session);
+         // Was the session destroyed as the result of a timeout or
+         // the undeployment of the containing webapp?
+         // If so, we'll just remove the expired session from the
+         // SSO.  If the session was logged out, we'll log out
+         // of all sessions associated with the SSO.
+         if (isSessionTimedOut(session) || isManagerStopped(session))
+         {
+            removeSession(ssoId, session);
+
+            // Quite poor.  We hijack the caller thread (the Tomcat background thread)
+            // to do our cleanup of expired sessions
+            processExpires();
+         }
+         else
+         {
+            // The session was logged out.
+            logout(ssoId);
+         }
       }
-      else
+      catch (Exception e)
       {
-         // The session was logged out.
-         logout(ssoId);
+         // Don't propagate back to the webapp; we don't want to disrupt
+         // the session expiration process
+         getContainer().getLogger().error("Caught exception updating SSO " + ssoId +
+                                          " following destruction of session " +
+                                          session.getId(), e);
       }
+   }
 
+   private boolean isSessionTimedOut(Session session)
+   {
+      return (session.getMaxInactiveInterval() > 0)
+               && (System.currentTimeMillis() - session.getLastAccessedTime() >=
+                  session.getMaxInactiveInterval() * 1000);
    }
 
+   private boolean isManagerStopped(Session session)
+   {
+      boolean stopped = false;
+      
+      Manager manager = session.getManager();
+      
+      if (manager instanceof ManagerBase)
+      {
+         ObjectName mgrName = ((ManagerBase)manager).getObjectName();
+         stopped = (!activeManagers.contains(mgrName));
+      }
+      else if (manager instanceof JBossManager)
+      {
+         ObjectName mgrName = ((JBossManager)manager).getObjectName();
+         stopped = (!activeManagers.contains(mgrName));
+      }
+      else if (manager instanceof Lifecycle)
+      {
+         stopped = (!activeManagers.contains(manager));
+      }
+      // else we have no way to tell, so assume not
+      
+      return stopped;
+   }
 
+   // ----------------------------------------------  LifecycleListener Methods
+
+
+   public void lifecycleEvent(LifecycleEvent event)
+   {
+      String type = event.getType();
+      if (Lifecycle.BEFORE_STOP_EVENT.equals(type) 
+            || Lifecycle.STOP_EVENT.equals(type) 
+            || Lifecycle.AFTER_STOP_EVENT.equals(type))
+      {
+         Lifecycle source = event.getLifecycle();
+         boolean removed;
+         if (source instanceof ManagerBase)
+         {
+            removed = activeManagers.remove(((ManagerBase)source).getObjectName());
+         }
+         else if (source instanceof JBossManager)
+         {
+            removed = activeManagers.remove(((JBossManager)source).getObjectName());
+         }
+         else
+         {
+            removed = activeManagers.remove(source);
+         }
+         
+         if (removed)
+         {
+            source.removeLifecycleListener(this);
+            
+            if (getContainer().getLogger().isDebugEnabled())
+            {
+                getContainer().getLogger().debug("ClusteredSSO: removed " +
+                        "stopped manager " + source.toString());
+            }
+         }
+         
+         // TODO consider getting the sessions and removing any from our sso's
+         // Idea is to cleanup after managers that don't destroy sessions
+         
+      }      
+   }
+   
+
    // ---------------------------------------------------------- Valve Methods
 
 
@@ -368,10 +596,11 @@
       }
 
       // Look up the cached Principal associated with this cookie value
+      String ssoId = cookie.getValue();
       if (getContainer().getLogger().isDebugEnabled())
-          getContainer().getLogger().debug(" Checking for cached principal for " + cookie.getValue());
+          getContainer().getLogger().debug(" Checking for cached principal for " + ssoId);
       SingleSignOnEntry entry = getSingleSignOnEntry(cookie.getValue());
-      if (entry != null)
+      if (entry != null && isValid(ssoId, entry))
       {
          Principal ssoPrinc = entry.getPrincipal();
          // have to deal with the fact that the entry may not have an
@@ -433,9 +662,48 @@
          reverse.put(session, ssoId);
       }
 
-      // If we made a change, notify any cluster
-      if (added && ssoClusterManager != null)
-         ssoClusterManager.addSession(ssoId, session);
+      // If we made a change, track the manager and notify any cluster
+      if (added)
+      {
+         Manager manager = session.getManager();
+         
+         // Prefer to cache an ObjectName to avoid risk of leaking a manager,
+         // so if the manager exposes one, use it
+         Object mgrKey = null;
+         if (manager instanceof ManagerBase)
+         {            
+            mgrKey = ((ManagerBase)manager).getObjectName();
+         }
+         else if (manager instanceof JBossManager)
+         {
+            mgrKey = ((JBossManager)manager).getObjectName();
+         }
+         else if (manager instanceof Lifecycle)
+         {
+            mgrKey = manager;
+         }
+         else {
+            getContainer().getLogger().warn("Manager for session " + 
+                      session.getId() +
+                      " does not implement Lifecycle; web app shutdown may " +
+                      " lead to incorrect SSO invalidations");
+         }
+         
+         if (mgrKey != null)
+         {
+            synchronized (activeManagers)
+            {
+               if (!activeManagers.contains(mgrKey))
+               {
+                  activeManagers.add(mgrKey);
+                  ((Lifecycle) manager).addLifecycleListener(this);
+               }
+            }
+         }
+         
+         if (ssoClusterManager != null)         
+            ssoClusterManager.addSession(ssoId, session);
+      }
    }
 
 
@@ -490,6 +758,11 @@
       if (getContainer().getLogger().isDebugEnabled())
           getContainer().getLogger().debug("Deregistering sso id '" + ssoId + "'");
 
+      
+      // It's possible we don't have the SSO locally but it's in
+      // the emptySSOs map; if so remove it
+      emptySSOs.remove(ssoId);
+      
       // Look up and remove the corresponding SingleSignOnEntry
       SingleSignOnEntry sso = null;
       synchronized (cache)
@@ -504,8 +777,8 @@
       Session sessions[] = sso.findSessions();
       for (int i = 0; i < sessions.length; i++)
       {
-         if (getContainer().getLogger().isTraceEnabled())
-             getContainer().getLogger().trace(" Invalidating session " + sessions[i]);
+         if (getContainer().getLogger().isDebugEnabled())
+             getContainer().getLogger().debug(" Invalidating session " + sessions[i]);
          // Remove from reverse cache first to avoid recursion
          synchronized (reverse)
          {
@@ -677,12 +950,12 @@
          reverse.remove(session);
       }
 
-      // If there are no sessions left in the SingleSignOnEntry,
-      // deregister the entry.
-      if (entry.getSessionCount() == 0)
-      {
-         deregister(ssoId);
-      }
+//      // If there are no sessions left in the SingleSignOnEntry,
+//      // deregister the entry.
+//      if (entry.getSessionCount() == 0)
+//      {
+//         deregister(ssoId);
+//      }
    }
 
 
@@ -846,6 +1119,35 @@
       }
 
    }
+   
+   /**
+    * Callback from the SSOManager when it detects an SSO without
+    * any active sessions across the cluster
+    */
+   void notifySSOEmpty(String ssoId)
+   {
+      Object obj = emptySSOs.put(ssoId, new Long(System.currentTimeMillis()));
+      
+      if (obj == null && getContainer().getLogger().isDebugEnabled())
+      {
+         getContainer().getLogger().debug("Notified that SSO " + ssoId + " is empty");
+      }
+   }
+   
+   /**
+    * Callback from the SSOManager when it detects an SSO that
+    * has active sessions across the cluster
+    */
+   void notifySSONotEmpty(String ssoId)
+   {
+      Object obj = emptySSOs.remove(ssoId);
+      
+      if (obj != null && getContainer().getLogger().isDebugEnabled())
+      {
+         getContainer().getLogger().debug("Notified that SSO " + ssoId + 
+                                          " is no longer empty");
+      }
+   }
 
    
    // -------------------------------------------------------  Private Methods
@@ -901,5 +1203,66 @@
          }
       }
    }
+   
+   
+   private void processExpires()
+   {
+      long now = 0L;
+      synchronized (mutex)
+      {
+         now = System.currentTimeMillis();
+      
+         if (now - lastProcessExpires > processExpiresInterval)
+         {
+            lastProcessExpires = now;
+         }
+         else
+         {
+             return;
+         }
+      }
+      
+      clearExpiredSSOs(now);
+   }
+   
+   private synchronized void clearExpiredSSOs(long now)
+   {      
+      for (Iterator iter = emptySSOs.entrySet().iterator(); iter.hasNext();)
+      {
+         Map.Entry entry = (Map.Entry) iter.next();
+         if ( (now - ((Long) entry.getValue()).longValue()) > maxEmptyLife)
+         {
+            String ssoId = (String) entry.getKey();
+            if (getContainer().getLogger().isDebugEnabled())
+            {
+               getContainer().getLogger().debug("Invalidating expired SSO " + ssoId);
+            }
+            logout(ssoId);
+         }
+      }      
+   }
+   
+   private boolean isValid(String ssoId, SingleSignOnEntry entry)
+   {
+      boolean valid = true;
+      if (entry.getSessionCount() == 0)
+      {
+         Long expired = (Long) emptySSOs.get(ssoId);
+         if (expired != null 
+               && (System.currentTimeMillis() - expired.longValue()) > maxEmptyLife)
+         {
+            valid = false;
+            
+            if (getContainer().getLogger().isDebugEnabled())
+            {
+               getContainer().getLogger().debug("Invalidating expired SSO " + ssoId);
+            }
+            
+            logout(ssoId);
+         }
+      }
+      
+      return valid;
+   }
 
 }
\ No newline at end of file

Modified: branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/TreeCacheSSOClusterManager.java
===================================================================
--- branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/TreeCacheSSOClusterManager.java	2006-11-02 20:44:41 UTC (rev 58020)
+++ branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/TreeCacheSSOClusterManager.java	2006-11-02 20:46:25 UTC (rev 58021)
@@ -1,15 +1,29 @@
 /*
- * JBoss, the OpenSource WebOS
+ * 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.
  *
- * Distributable under LGPL license.
- * See terms of license at gnu.org.
+ * 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.tc5.sso;
 
 import java.io.Serializable;
 import java.security.Principal;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.Set;
 
 import javax.management.MBeanServer;
@@ -91,20 +105,19 @@
    // -------------------------------------------------------  Instance Fields
    
    /**
-    * List of SSO ids which this object is currently storing to the cache
+    * SSO id which the thread is currently storing to the cache
     */
-   private LinkedList beingLocallyAdded = new LinkedList();
+   private ThreadLocal beingLocallyAdded = new ThreadLocal();
 
    /**
-    * List of SSO ids which this object is currently removing from the cache
+    * SSO id which a thread is currently removing from the cache
     */
-   private LinkedList beingLocallyRemoved = new LinkedList();
+   private ThreadLocal beingLocallyRemoved = new ThreadLocal();
 
    /**
-    * List of SSO ids which are being deregistered due to removal on another
-    * node
+    * SSO id which the thread is deregistering due to removal on another node
     */
-   private LinkedList beingRemotelyRemoved = new LinkedList();
+   private ThreadLocal beingRemotelyRemoved = new ThreadLocal();
 
    /**
     * ObjectName of the TreeCache
@@ -117,12 +130,6 @@
    private String cacheName = null;
 
    /**
-    * CredentialUpdater used to allow asynchronous updates of
-    * SSO credentials
-    */
-   private CredentialUpdater credentialUpdater = null;
-
-   /**
     * Transaction Manager
     */
    private TransactionManager tm = null;
@@ -167,6 +174,11 @@
     */
    private boolean missingCacheErrorLogged = false;
    
+   /**
+    * Our node's address in the cluster.
+    */
+   private Serializable localAddress = null;
+   
    // ----------------------------------------------------------  Constructors
 
    
@@ -177,9 +189,21 @@
    {
       // Find our MBeanServer
       server = MBeanServerLocator.locateJBoss();
+      if (server == null)
+         server = MBeanServerLocator.locate();
    }
+
    
+   /**
+    * Creates a new TreeCacheSSOClusterManager that works with the given
+    * MBeanServer.  This constructor is only intended for use in unit testing.
+    */
+   public TreeCacheSSOClusterManager(MBeanServer server)
+   {
+      this.server = server;
+   }
    
+   
    // ------------------------------------------------------------  Properties
   
    public String getCacheName()
@@ -216,7 +240,7 @@
          return;
       }
 
-      removeAsTreeCacheListener(cacheObjectName);
+      removeAsTreeCacheListener();
       this.tm = null;
       
       this.cacheObjectName = objectName;
@@ -238,16 +262,11 @@
                "before ClusteredSingleSignOn can handle requests");
          }
       }
-      else if (started)
-      {
-         // Confirm the cache has a transaction manager
-         // and fail if not found
-         findTransactionManager();
-      }
    }
    
    // -----------------------------------------------------  SSOClusterManager
 
+
    /**
     * Notify the cluster of the addition of a Session to an SSO session.
     *
@@ -261,9 +280,8 @@
          return;
       }
 
-      if (false == isTreeCacheAvailable(false))
+      if (!checkTreeCacheAvailable())
       {
-         logMissingCacheError();
          return;
       }
 
@@ -277,6 +295,11 @@
       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)
@@ -286,7 +309,7 @@
             tm.begin();
          
          Set sessions = getSessionSet(fqn, true);
-         sessions.add(session.getId());
+         sessions.add(new SessionAddress(session.getId(), localAddress));
          putInTreeCache(fqn, sessions);
       }
       catch (Exception e)
@@ -296,7 +319,7 @@
             if(doTx)
                tm.setRollbackOnly();
          }
-         catch (Exception x)
+         catch (Exception ignored)
          {
          }
          String sessId = (session == null ? "NULL" : session.getId());
@@ -345,23 +368,20 @@
     */
    public void logout(String ssoId)
    {
-      if (false == isTreeCacheAvailable(false))
+      if (!checkTreeCacheAvailable())
       {
-         logMissingCacheError();
          return;
       }
       
       // Check whether we are already handling this removal 
-      //synchronized (beingLocallyRemoved)
+      if (ssoId.equals(beingLocallyRemoved.get()))
       {
-         if (beingLocallyRemoved.contains(ssoId))
-         {
-            return;
-         }         
-         // Add this SSO to our list of in-process local removals so
-         // this.nodeRemoved() will ignore the removal
-         beingLocallyRemoved.add(ssoId);
-      }
+         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())
       {
@@ -382,10 +402,7 @@
       }
       finally
       {
-         //synchronized (beingLocallyRemoved)
-         {
-            beingLocallyRemoved.remove(ssoId);
-         }
+         beingLocallyRemoved.set(null);
       }
    }
 
@@ -401,20 +418,16 @@
     */
    public SingleSignOnEntry lookup(String ssoId)
    {
-      if (false == isTreeCacheAvailable(false))
+      if (!checkTreeCacheAvailable())
       {
-         logMissingCacheError();
          return null;
       }
 
       SingleSignOnEntry entry = null;
       // Find the latest credential info from the cluster
       Fqn fqn = getCredentialsFqn(ssoId);
-      //UserTransaction tx = null;
       try
       {
-         //tx = getNewTransaction();
-         //tx.begin();
          SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn);
          if (data != null)
          {
@@ -423,20 +436,9 @@
                data.getUsername(),
                data.getPassword());
          }
-         //tx.commit();
       }
       catch (Exception e)
       {
-         /*
-         if (tx != null)
-         {
-            try
-            {
-               tx.rollback();
-            }
-            catch (Exception x) {}
-         }
-         */
          log.error("caught exception looking up SSOCredentials for SSO id " +
             ssoId, e);
       }
@@ -456,9 +458,8 @@
    public void register(String ssoId, String authType,
       String username, String password)
    {
-      if (false == isTreeCacheAvailable(false))
+      if (!checkTreeCacheAvailable())
       {
-         logMissingCacheError();
          return;
       }
 
@@ -479,33 +480,32 @@
     */
    public void removeSession(String ssoId, Session session)
    {
-      if (false == isTreeCacheAvailable(false))
+      if (ssoId == null || session == null)
       {
-         logMissingCacheError();
          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
-      //synchronized(beingRemotelyRemoved)
+      if (ssoId.equals(beingRemotelyRemoved.get()))
       {
-         if (beingRemotelyRemoved.contains(ssoId))
-         {
-            return;
-         }
+         return;
       }
-
-      if (log.isTraceEnabled())
-      {
-         log.trace("removeSession(): removing Session " + session.getId() +
-            " from cached session set for SSO " + ssoId);
-      }
-
+      
       Fqn fqn = getSessionsFqn(ssoId);
       boolean doTx = false;
-      boolean removing = 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.
@@ -518,23 +518,29 @@
          Set sessions = getSessionSet(fqn, false);
          if (sessions != null)
          {
-            sessions.remove(session.getId());
-            if (sessions.size() == 0)
+            sessions.remove(new SessionAddress(session.getId(), localAddress));
+
+            if (log.isTraceEnabled())
             {
-               // Add this SSO to our list of in-process local removals so
-               // this.nodeRemoved() will ignore the removal
-               //synchronized (beingLocallyRemoved)
-               {
-                  beingLocallyRemoved.add(ssoId);
-               }
-               removing = true;
-               // No sessions left; remove node
-               removeFromTreeCache(getSingleSignOnFqn(ssoId));
+               log.trace("removed session " + session.getId() +
+                  " from cached session set for SSO " + ssoId + " -- " +
+                  sessions.size() + " sessions remain");
             }
-            else
-            {
+            
+//            if (sessions.size() == 0)
+//            {               
+//               // No sessions left; remove node
+//               
+//               // Add this SSO to our list of in-process local removals so
+//               // this.nodeRemoved() will ignore the removal
+//               removing = true;
+//               beingLocallyRemoved.set(ssoId);
+//               removeFromTreeCache(getSingleSignOnFqn(ssoId));
+//            }
+//            else
+//            {
                putInTreeCache(fqn, sessions);
-            }
+//            }
          }
       }
       catch (Exception e)
@@ -554,21 +560,18 @@
       }
       finally
       {
-         try
-         {
-            if (removing)
-            {
-               //synchronized (beingLocallyRemoved)
-               {
-                  beingLocallyRemoved.remove(ssoId);
-               }
-            }
-         }
-         finally
-         {
+//         try
+//         {
+//            if (removing)
+//            {
+//               beingLocallyRemoved.set(null);
+//            }
+//         }
+//         finally
+//         {
             if (doTx)
                endTransaction();
-         }
+//         }
       }
    }
 
@@ -586,9 +589,8 @@
    public void updateCredentials(String ssoId, String authType,
       String username, String password)
    {
-      if (false == isTreeCacheAvailable(false))
+      if (!checkTreeCacheAvailable())
       {
-         logMissingCacheError();
          return;
       }
 
@@ -660,20 +662,17 @@
    public void nodeRemoved(Fqn fqn)
    {
       String ssoId = getIdFromFqn(fqn);
+      
+      if (ssoId == null)
+         return;
 
       // Ignore messages generated by our own activity
-      //synchronized(beingLocallyRemoved)
+      if (ssoId.equals(beingLocallyRemoved.get()))
       {
-         if (beingLocallyRemoved.contains(ssoId))
-         {
-            return;
-         }
+         return;
       }
       
-      //synchronized (beingRemotelyRemoved)
-      {
-         beingRemotelyRemoved.add(ssoId);
-      }
+      beingRemotelyRemoved.set(ssoId);
 
       try
       {
@@ -686,10 +685,7 @@
       }
       finally
       {
-         //synchronized(beingRemotelyRemoved)
-         {
-            beingRemotelyRemoved.remove(ssoId);
-         }
+         beingRemotelyRemoved.set(null);
       }
 
    }
@@ -712,21 +708,30 @@
     */
    public void nodeModified(Fqn fqn)
    {
-      // We are only interested in changes to the CREDENTIALS node
-      if (CREDENTIALS.equals(getTypeFromFqn(fqn)) == false)
+      String type = getTypeFromFqn(fqn);
+      if (CREDENTIALS.equals(type))
       {
-         return;
+         handleCredentialUpdate(fqn);
       }
+      else if (SESSIONS.equals(type))
+      {
+         handleSessionSetChange(fqn);
+      }
+   }
 
-      String ssoId = getIdFromFqn(fqn);
 
+   /**
+    *  
+    * @param fqn an Fqn that points to the CREDENTIALS node of an SSO
+    */
+   private void handleCredentialUpdate(Fqn fqn)
+   {
+      String ssoId = getIdFromFqn(fqn); // won't be null per the API contract
+
       // Ignore invocations that come as a result of our additions
-      //synchronized(beingLocallyAdded)
+      if (ssoId.equals(beingLocallyAdded.get()))
       {
-         if (beingLocallyAdded.contains(ssoId))
-         {
-            return;
-         }
+         return;
       }
 
       SingleSignOnEntry sso = ssoValve.localLookup(ssoId);
@@ -742,11 +747,72 @@
       }
 
       // Put this SSO in the queue of those to be updated
-      credentialUpdater.enqueue(sso, ssoId);
-   }
+//      credentialUpdater.enqueue(sso, ssoId);
+      try
+      {
+         SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn);
+         if (data != null)
+         {
+            // We want to release our read lock quickly, so get the needed
+            // data from the cache, commit the tx, and then use the data
+            String authType = data.getAuthType();
+            String username = data.getUsername();
+            String password = data.getPassword();
 
+            if (log.isTraceEnabled())
+            {
+               log.trace("CredentialUpdater: Updating credentials for SSO " + sso);
+            }
 
+            synchronized (sso)
+            {
+               // Use the existing principal
+               Principal p = sso.getPrincipal();
+               sso.updateCredentials(p, authType, username, password);
+            }
+         }
+      }
+      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
+    */
+   private void handleSessionSetChange(Fqn fqn)
+   {
+      // Here we *want* to handle local activity as well as remote,
+      // as the distributed cache set of sessions is the only
+      // complete representation
+      
+      String ssoId = getIdFromFqn(fqn);
+      try      
+      {
+         Set sessions = (Set) getFromTreeCache(fqn);
+         if (sessions == null)
+         {
+            // don't think we'd get this event; 
+            // in any case we don't deal with this
+         }
+         else if (sessions.size() == 0)
+         {
+            ssoValve.notifySSOEmpty(ssoId);            
+         }
+         else
+         {
+            ssoValve.notifySSONotEmpty(ssoId);
+         }
+      }
+      catch (Exception e)
+      {
+         log.error("failed in check for empty SSO " + ssoId, e);
+      }
+   }
+
+   /**
     * Does nothing
     */
    public void viewChange(View new_view)
@@ -818,15 +884,12 @@
          throw new LifecycleException
             ("TreeCacheSSOClusterManager already Started");
       }
-      
-      // Start the thread we use to clear nodeModified events
-      credentialUpdater = new CredentialUpdater();
 
       try 
       {
-         if (tm == null && isTreeCacheAvailable(true))
+         if (isTreeCacheAvailable(true))
          {
-            findTransactionManager();
+            integrateWithCache();
          }
       }
       catch (Exception e)
@@ -859,9 +922,7 @@
          throw new LifecycleException
             ("TreeCacheSSOClusterManager not Started");
       }
-
-      credentialUpdater.stop();
-
+      
       started = false;
 
       // Notify our interested LifecycleListeners
@@ -903,7 +964,12 @@
     */
    private String getIdFromFqn(Fqn fqn)
    {
-      return (String) fqn.get(1);
+      String id = null;
+      if (fqn.size() > 1 && SSO.equals(fqn.get(0)))
+      {
+         id = (String) fqn.get(1);
+      }
+      return id;
    }
 
    private Set getSessionSet(Fqn fqn, boolean create)
@@ -922,25 +988,30 @@
     * object.
     *
     * @param fqn the Fully Qualified Name used by TreeCache
-    * @return the last element in the Fqn -- either
-    *         {@link #CREDENTIALS CREDENTIALS} or {@link #SESSIONS SESSIONS}.
+    * @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)
    {
-      return (String) fqn.get(fqn.size() - 1);
+      String type = null;
+      if (fqn.size() > 2 && SSO.equals(fqn.get(0)))
+         type = (String) fqn.get(2);
+      return type;
    }
    
    /**
+    * 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.
+    * Also get our cluster-wide unique local address from the cache.
     * 
     * @throws Exception
     */
-   private void findTransactionManager() throws Exception
-   {
-      tm = (TransactionManager) server.invoke(getCacheObjectName(), 
-                                              "getTransactionManager", 
-                                              null, new String[] {});                  
+   private void configureFromCache() throws Exception
+   {  
+      tm = (TransactionManager) server.getAttribute(getCacheObjectName(), 
+                                                    "TransactionManager");
 
       if (tm == null) 
       {
@@ -949,6 +1020,15 @@
                                          "configure a valid " +
                                          "TransactionManagerLookupClass");
       }
+      
+      // Find out our address
+      Object address = server.getAttribute(cacheObjectName, "LocalAddress");
+      // 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
+         localAddress = address.toString();
    }
    
 
@@ -994,13 +1074,19 @@
             {
                try
                {
-                  registerAsTreeCacheListener(cacheObjectName);
+                  // If Tomcat5 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 registering as listener to " +
-                     cacheObjectName, e);
+                  log.error("Caught exception configuring from cache " +
+                            cacheObjectName, e);
                   available = false;
                }
             }
@@ -1009,6 +1095,14 @@
       }
       return treeCacheAvailable;
    }
+   
+   private boolean checkTreeCacheAvailable()
+   {
+      boolean avail = isTreeCacheAvailable(false);
+      if (!avail)
+         logMissingCacheError();
+      return avail;
+   }
 
    private void putInTreeCache(Fqn fqn, Object data) throws Exception
    {
@@ -1016,16 +1110,26 @@
       server.invoke(getCacheObjectName(), "put", args, PUT_SIGNATURE);
    }
 
+   private void integrateWithCache() throws Exception
+   {
+      // Ensure we have a transaction manager and a cluster-wide unique address
+      configureFromCache();
+      
+      registerAsTreeCacheListener();
+      
+      log.debug("Successfully integrated with cache service " + cacheObjectName);
+   }
+
+
    /**
     * Invokes an operation on the JMX server to register ourself as a
     * listener on the TreeCache service.
     *
     * @throws Exception
     */
-   private void registerAsTreeCacheListener(ObjectName listenTo)
-      throws Exception
+   private void registerAsTreeCacheListener() throws Exception
    {
-      server.invoke(listenTo, "addTreeCacheListener",
+      server.invoke(cacheObjectName, "addTreeCacheListener",
          new Object[]{this},
          new String[]{TreeCacheListener.class.getName()});
       registeredAsListener = true;
@@ -1038,12 +1142,11 @@
     *
     * @throws Exception
     */
-   private void removeAsTreeCacheListener(ObjectName removeFrom)
-      throws Exception
+   private void removeAsTreeCacheListener() throws Exception
    {
-      if (registeredAsListener && removeFrom != null)
+      if (registeredAsListener && cacheObjectName != null)
       {
-         server.invoke(removeFrom, "removeTreeCacheListener",
+         server.invoke(cacheObjectName, "removeTreeCacheListener",
             new Object[]{this},
             new String[]{TreeCacheListener.class.getName()});
       }
@@ -1076,41 +1179,23 @@
       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
-      //synchronized (beingLocallyAdded)
-      {
-         beingLocallyAdded.add(ssoId);
-      }
-      //UserTransaction tx = null;
+      beingLocallyAdded.set(ssoId);
+      
       try
       {
-         //tx = getNewTransaction();
-         //tx.begin();
          putInTreeCache(getCredentialsFqn(ssoId), data);
-         //tx.commit();
       }
       catch (Exception e)
       {
-         /*
-         if (tx != null)
-         {
-            try
-            {
-               tx.rollback();
-            }
-            catch (Exception x) {}
-         }
-         */
          log.error("Exception attempting to add TreeCache nodes for SSO " +
             ssoId, e);
       }
       finally
       {
-         //synchronized (beingLocallyAdded)
-         {
-            beingLocallyAdded.remove(ssoId);
-         }
+         beingLocallyAdded.set(null);
       }
    }
 
@@ -1144,219 +1229,6 @@
       }
    }
 
-   // ---------------------------------------------------------  Inner Classes
-
-   /**
-    * Spawns a thread to handle updates of credentials
-    * 
-    * TODO  This was added in the early days of JBossCache when invoking
-    *       a get() on a node in the middle of a TreeCacheListener 
-    *       notification would cause a deadlock.  Should not be necessary now.
-    */
-   private class CredentialUpdater
-      implements Runnable
-   {
-      private HashSet awaitingUpdate = new HashSet();
-      private Thread updateThread;
-      private boolean updateThreadSleeping = false;
-      private boolean queueEmpty = true;
-      private boolean stopped = false;
-
-      private CredentialUpdater()
-      {
-         updateThread =
-            new Thread(this, "SSOClusterManager.CredentialUpdater");
-         updateThread.setDaemon(true);
-         updateThread.start();
-      }
-
-      // ------------------------------------------------------  Runnable
-
-      public void run()
-      {
-         while (!stopped)
-         {
-            // Ensure that no runtime exceptions kill this thread
-            try
-            {
-               updateThreadSleeping = false;
-               // Get the current list of ids awaiting processing
-               SSOWrapper[] ssos = null;
-               synchronized (awaitingUpdate)
-               {
-                  ssos = new SSOWrapper[awaitingUpdate.size()];
-                  ssos = (SSOWrapper[]) awaitingUpdate.toArray(ssos);
-                  awaitingUpdate.clear();
-                  queueEmpty = true;
-               }
-
-               // Handle the credential update
-               for (int i = 0; i < ssos.length; i++)
-               {
-                  processUpdate(ssos[i]);
-               }
-
-               // Wait for another invocation of enqueue().  But,
-               // first have to check in case it was invoked while we
-               // were processing the previous bunch
-               if (queueEmpty)
-               {
-                  try
-                  {
-                     // There is a slight possibility here of a race condition
-                     // between the above check for queueEmpty and another 
-                     // thread accessing enqueue()'s check of 
-                     // updateThreadSleeping.  If this happens, the update
-                     // will not be processed by the local node until the
-                     // updateThread wakes up (30 secs) or is interrupted by 
-                     // another update.  This situation is quite unlikely,
-                     // as updates only happen 1) in odd configurations where
-                     // CLIENT-CERT authentication is used for some apps and 
-                     // FORM or BASIC are used for others and 2) the user has
-                     // first logged in to a CLIENT-CERT app and later logs in
-                     // to a FORM/BASIC app.  If such a race condition were to 
-                     // occur, the only downside would be that if the user 
-                     // accessed a FORM/BASIC app on this node before the local
-                     // update is processed, they would have to log in again.
-                     updateThreadSleeping = true;
-                     updateThread.sleep(30000);
-                  }
-                  catch (InterruptedException e)
-                  {
-                     if (log.isTraceEnabled())
-                     {
-                        log.trace("CredentialUpdater: interrupted");
-                     }
-                     // process the next bunch
-                  }
-               }
-               else if (log.isTraceEnabled())
-               {
-                  log.trace("CredentialUpdater: more updates added while " +
-                     "handling existing updates");
-               }
-            }
-            catch (Exception e)
-            {
-               log.error("CredentialUpdater thread caught an exception", e);
-            }
-         }
-      }
-
-      // -------------------------------------------------  Private Methods
-
-      /**
-       * Adds an SSO id to the set of those awaiting credential updating, and
-       * interrupts the update handler thread to notify it of the addition.
-       *
-       * @param sso the id of the SSO session whose local credentials
-       *            are to be updated
-       */
-      private void enqueue(SingleSignOnEntry sso, String ssoId)
-      {
-         synchronized (awaitingUpdate)
-         {
-            awaitingUpdate.add(new SSOWrapper(sso, ssoId));
-            queueEmpty = false;
-         }
-         // Interrupt the update thread so it wakes up to process
-         // the enqueued update.  Only do this if its "sleeping" flag
-         // is set so we don't inadvertently interrupt it while its
-         // blocked waiting for a TreeCache lock to clear
-         if (updateThreadSleeping)
-         {
-            updateThread.interrupt();
-         }
-      }
-
-      private void processUpdate(SSOWrapper wrapper)
-      {
-         if (wrapper.sso.getCanReauthenticate())
-         {
-            // No need to update
-            return;
-         }
-
-         Fqn fqn = getCredentialsFqn(wrapper.id);
-         //UserTransaction tx = null;
-         try
-         {
-            //tx = getNewTransaction();
-            //tx.begin();
-            SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn);
-            if (data != null)
-            {
-               // We want to release our read lock quickly, so get the needed
-               // data from the cache, commit the tx, and then use the data
-               String authType = data.getAuthType();
-               String username = data.getUsername();
-               String password = data.getPassword();
-               //tx.commit();
-
-               if (log.isTraceEnabled())
-               {
-                  log.trace("CredentialUpdater: Updating credentials for SSO " +
-                     wrapper.sso);
-               }
-
-               synchronized (wrapper.sso)
-               {
-                  // Use the existing principal
-                  Principal p = wrapper.sso.getPrincipal();
-                  wrapper.sso.updateCredentials(p, authType, username, password);
-               }
-            }
-            /*
-            else
-            {
-               tx.commit();
-            }
-            */
-
-         }
-         catch (Exception e)
-         {
-            /*
-            if (tx != null)
-            {
-               try
-               {
-                  tx.rollback();
-               }
-               catch (Exception x) {}
-            }
-            */
-            log.error("Exception attempting to get SSOCredentials from " +
-               "TreeCache node " + fqn.toString(), e);
-         }
-      }
-
-      /**
-       * Stops the update handler thread.
-       */
-      private void stop()
-      {
-         stopped = true;
-      }
-
-   }  // end CredentialUpdater
-
-
-   /**
-    * Wrapper class that holds a SingleSignOnEntry and its id
-    */
-   private class SSOWrapper
-   {
-      private SingleSignOnEntry sso = null;
-      private String id = null;
-
-      private SSOWrapper(SingleSignOnEntry entry, String ssoId)
-      {
-         this.sso = entry;
-         this.id = ssoId;
-      }
-   }
-
    // ---------------------------------------------------------  Outer Classes
 
    /**
@@ -1421,6 +1293,135 @@
       }
 
    } // end SSOCredentials
+   
+   static class SessionAddress implements Serializable
+   {
+      /** The serialVersionUID */
+      private static final long serialVersionUID = -3702932999380140004L;
+      
+      Serializable address;
+      String sessionId;
+      
+      SessionAddress(String sessionId, Serializable address)
+      {
+         this.sessionId = sessionId;
+         this.address   = address;
+      }
 
+      public boolean equals(Object obj)
+      {
+         if (this == obj)
+            return true;
+         
+         if (!(obj instanceof SessionAddress))
+            return false;
+         
+         SessionAddress other = (SessionAddress) obj;
+         
+         return (sessionId.equals(other.sessionId) 
+                 && address.equals(other.address));
+      }
+
+      public int hashCode()
+      {
+         int total = (19 * 43) + sessionId.hashCode();
+         return ((total * 43) + address.hashCode());
+      }
+      
+      
+   }
+
+// DISABLED UNTIL 4.0.6.CR1
+//   /**
+//    * 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 DeadMemberCleaner implements Runnable
+//   {    
+//      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();)
+//               {
+//                  cleanDeadMembersFromSSO((String) iter.next());
+//                  
+//               }
+//            }
+//            catch (Exception e)
+//            {
+//               log.error("Caught exception cleaning sessions from dead cluster members from SSOs ", e);
+//            }
+//         }
+//      }
+//      
+//      private void cleanDeadMembersFromSSO(String ssoId)
+//      {
+//         Fqn fqn = getSessionsFqn(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 sessions = getSessionSet(fqn, false, true);
+//            if (sessions != null && sessions.size() > 0)
+//            {
+//               boolean changed = false;
+//               for (Iterator iter = sessions.iterator(); iter.hasNext();)
+//               {
+//                  SessionAddress session = (SessionAddress) iter.next();
+//                  if (!currentView.containsMember((Address) session.address))
+//                  {
+//                     iter.remove();
+//                     changed = true;
+//                  }
+//               }
+//               
+//               if (changed)
+//               {                  
+//                  if (sessions.size() == 0)
+//                  {
+//                     ssoValve.notifySSOEmpty(ssoId);
+//                  }
+//                  
+//                  putInTreeCache(fqn, sessions, true);
+//               }
+//            }
+//         }
+//         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 TreeCacheSSOClusterManager
 

Modified: branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/mbeans-descriptors.xml
===================================================================
--- branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/mbeans-descriptors.xml	2006-11-02 20:44:41 UTC (rev 58020)
+++ branches/JBoss_4_0_3_SP1_JBAS-3808/tomcat/src/main/org/jboss/web/tomcat/tc5/sso/mbeans-descriptors.xml	2006-11-02 20:46:25 UTC (rev 58021)
@@ -20,6 +20,19 @@
          description="Should we attempt to reauthenticate each request against the security Realm?"
          type="boolean"/>
 
+      <attribute name="maxEmptyLife"
+         description="The maximum number of seconds an SSO with no active sessions will be usable by a request"
+         type="int"/>
+
+      <attribute name="processExpiresInterval"
+         description="The maximum number of seconds an SSO with no active sessions will be usable by a request"
+         type="int"/>
+
+      <attribute name="lastProcessExpires"
+         writeable="false"
+         description="The timestamp of the start of the last check for overaged SSO's with no active sessions."
+         type="long"/>
+
       <attribute name="clusterManager"
          description="SSOClusterManager to use for cluster support"
          type="org.jboss.web.tomcat.tc5.sso.SSOClusterManager"/>




More information about the jboss-cvs-commits mailing list