[jboss-cvs] JBossAS SVN: r77453 - in trunk: tomcat/src/main/org/jboss/web/tomcat/service/sso and 3 other directories.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Mon Aug 25 16:32:28 EDT 2008


Author: bstansberry at jboss.com
Date: 2008-08-25 16:32:27 -0400 (Mon, 25 Aug 2008)
New Revision: 77453

Added:
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/impl/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/impl/TreeCacheSSOClusterManager.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/FullyQualifiedSessionId.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOClusterManager.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOCredentials.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOLocalManager.java
Removed:
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/SSOClusterManager.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/TreeCacheSSOClusterManager.java
Modified:
   trunk/testsuite/src/main/org/jboss/test/cluster/defaultcfg/simpleweb/test/TreeCacheSSOClusterManagerUnitTestCase.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/ClusteredSingleSignOn.java
Log:
[JBAS-5889] Refactor ClusteredSSO interaction with JBC into pluggable component

Modified: trunk/testsuite/src/main/org/jboss/test/cluster/defaultcfg/simpleweb/test/TreeCacheSSOClusterManagerUnitTestCase.java
===================================================================
--- trunk/testsuite/src/main/org/jboss/test/cluster/defaultcfg/simpleweb/test/TreeCacheSSOClusterManagerUnitTestCase.java	2008-08-25 19:26:41 UTC (rev 77452)
+++ trunk/testsuite/src/main/org/jboss/test/cluster/defaultcfg/simpleweb/test/TreeCacheSSOClusterManagerUnitTestCase.java	2008-08-25 20:32:27 UTC (rev 77453)
@@ -58,7 +58,8 @@
 import org.jboss.test.cluster.web.mocks.MockHost;
 import org.jboss.util.threadpool.BasicThreadPool;
 import org.jboss.web.tomcat.service.sso.ClusteredSingleSignOn;
-import org.jboss.web.tomcat.service.sso.TreeCacheSSOClusterManager;
+import org.jboss.web.tomcat.service.sso.jbcintegration.impl.TreeCacheSSOClusterManager;
+import org.jboss.web.tomcat.service.sso.jbcintegration.spi.FullyQualifiedSessionId;
 import org.jgroups.Address;
 import org.jgroups.View;
 import org.jgroups.ViewId;
@@ -148,7 +149,7 @@
          
          MockSSOValve localValve = new MockSSOValve();
          localValve.setClusterManager(localSSOManager);
-         localSSOManager.setSingleSignOnValve(localValve);
+         localSSOManager.setSSOLocalManager(localValve);
          localSSOManager.start();
          
          // Create an SSO that will have two sessions from local valve
@@ -156,11 +157,11 @@
          
          Manager localSessMgr1 = getSessionManager("A");
          Session sess1 = new MockSession(localSessMgr1, "1");
-         localSSOManager.addSession("1", sess1);
+         localSSOManager.addSession("1", new FullyQualifiedSessionId(sess1));
          
          Manager localSessMgr2 = getSessionManager("B");
          Session sess2 = new MockSession(localSessMgr2, "2");
-         localSSOManager.addSession("1", sess2);
+         localSSOManager.addSession("1", new FullyQualifiedSessionId(sess2));
          
          // Confirm that data is cached properly
          assertEquals("SSO 1 has correct number of sessions", 2, localSSOManager.getSessionCount("1"));   
@@ -206,7 +207,7 @@
          
          MockSSOValve localValve = new MockSSOValve();
          localValve.setClusterManager(localSSOManager);
-         localSSOManager.setSingleSignOnValve(localValve);
+         localSSOManager.setSSOLocalManager(localValve);
          localSSOManager.start();
          
          // Create an SSO that will have two sessions from local valve
@@ -214,11 +215,11 @@
          
          Manager localSessMgr1 = getSessionManager("A");
          Session sess1 = new MockSession(localSessMgr1, "1");
-         localSSOManager.addSession("1", sess1);
+         localSSOManager.addSession("1", new FullyQualifiedSessionId(sess1));
          
          Manager localSessMgr2 = getSessionManager("B");
          Session sess2 = new MockSession(localSessMgr2, "2");
-         localSSOManager.addSession("1", sess2);
+         localSSOManager.addSession("1", new FullyQualifiedSessionId(sess2));
          
          // Confirm that data is cached properly
          assertEquals("SSO 1 has correct number of sessions", 2, localSSOManager.getSessionCount("1"));   
@@ -262,7 +263,7 @@
       
       MockSSOValve localValve = new MockSSOValve();
       localValve.setClusterManager(localSSOManager);
-      localSSOManager.setSingleSignOnValve(localValve);
+      localSSOManager.setSSOLocalManager(localValve);
       localSSOManager.start();
       
       // Create an SSO that will have two sessions from local valve
@@ -270,11 +271,11 @@
       
       Manager localSessMgr1 = getSessionManager("A");
       Session sess1 = new MockSession(localSessMgr1, "1");
-      localSSOManager.addSession("1", sess1);
+      localSSOManager.addSession("1", new FullyQualifiedSessionId(sess1));
       
       Manager localSessMgr2 = getSessionManager("B");
       Session sess2 = new MockSession(localSessMgr2, "2");
-      localSSOManager.addSession("1", sess2);
+      localSSOManager.addSession("1", new FullyQualifiedSessionId(sess2));
       
       // Confirm that data is cached properly
       assertEquals("SSO 1 has correct number of sessions", 2, localSSOManager.getSessionCount("1"));
@@ -319,7 +320,7 @@
       
       MockSSOValve localValve = new MockSSOValve();
       localValve.setClusterManager(localSSOManager);
-      localSSOManager.setSingleSignOnValve(localValve);
+      localSSOManager.setSSOLocalManager(localValve);
       
       try
       {
@@ -381,7 +382,7 @@
       
       MockSSOValve localValve = new MockSSOValve();
       localValve.setClusterManager(localSSOManager);
-      localSSOManager.setSingleSignOnValve(localValve);
+      localSSOManager.setSSOLocalManager(localValve);
       localSSOManager.start();
       
       assertEquals("Thread pool usage as expected", usePool, localSSOManager.isUsingThreadPool());
@@ -396,7 +397,7 @@
       
       MockSSOValve remoteValve = new MockSSOValve();
       remoteValve.setClusterManager(remoteSSOManager);
-      remoteSSOManager.setSingleSignOnValve(localValve);
+      remoteSSOManager.setSSOLocalManager(localValve);
       remoteSSOManager.start();
       
       
@@ -405,11 +406,11 @@
       
       Manager localSessMgr1 = getSessionManager("A");
       Session sess1 = new MockSession(localSessMgr1, "1");
-      localSSOManager.addSession("1", sess1);
+      localSSOManager.addSession("1", new FullyQualifiedSessionId(sess1));
       
       Manager remoteSessMgr1 = getSessionManager("B");
       Session sess2 = new MockSession(remoteSessMgr1, "2");
-      remoteSSOManager.addSession("1", sess2);
+      remoteSSOManager.addSession("1", new FullyQualifiedSessionId(sess2));
       
       
       // Create another SSO with sessions only from remote
@@ -417,11 +418,11 @@
       
       Manager remoteSessMgr2 = getSessionManager("C");
       Session sess3 = new MockSession(remoteSessMgr2, "3");
-      remoteSSOManager.addSession("2", sess3);
+      remoteSSOManager.addSession("2", new FullyQualifiedSessionId(sess3));
       
       Manager remoteSessMgr3 = getSessionManager("D");
       Session sess4 = new MockSession(remoteSessMgr3, "4");
-      remoteSSOManager.addSession("2", sess4);
+      remoteSSOManager.addSession("2", new FullyQualifiedSessionId(sess4));
       
       
       // Create a third SSO that will have sessions from both valves
@@ -430,11 +431,11 @@
       
       Manager localSessMgr2 = getSessionManager("E");
       Session sess5 = new MockSession(localSessMgr2, "5");
-      localSSOManager.addSession("3", sess5);
+      localSSOManager.addSession("3", new FullyQualifiedSessionId(sess5));
       
       Manager remoteSessMgr4 = getSessionManager("E");
       Session sess6 = new MockSession(remoteSessMgr4, "5");
-      remoteSSOManager.addSession("3", sess6);
+      remoteSSOManager.addSession("3", new FullyQualifiedSessionId(sess6));
       
       
       // Create a fourth SSO that will have two sessions from local valve
@@ -442,11 +443,11 @@
       
       Manager localSessMgr3 = getSessionManager("F");
       Session sess7 = new MockSession(localSessMgr3, "7");
-      localSSOManager.addSession("4", sess7);
+      localSSOManager.addSession("4", new FullyQualifiedSessionId(sess7));
       
       Manager localSessMgr4 = getSessionManager("G");
       Session sess8 = new MockSession(localSessMgr4, "8");
-      localSSOManager.addSession("4", sess8);
+      localSSOManager.addSession("4", new FullyQualifiedSessionId(sess8));
       
       
       // Create a fifth SSO with sessions only from remote, same session id
@@ -455,11 +456,11 @@
       
       Manager remoteSessMgr5 = getSessionManager("H");
       Session sess9 = new MockSession(remoteSessMgr5, "9");
-      remoteSSOManager.addSession("5", sess9);
+      remoteSSOManager.addSession("5", new FullyQualifiedSessionId(sess9));
       
       Manager remoteSessMgr6 = getSessionManager("I");
       Session sess10 = new MockSession(remoteSessMgr6, "9");
-      remoteSSOManager.addSession("5", sess10);
+      remoteSSOManager.addSession("5", new FullyQualifiedSessionId(sess10));
       
       // Confirm that data is cached properly
       assertEquals("SSO 1 has correct number of sessions", 2, localSSOManager.getSessionCount("1"));
@@ -519,7 +520,7 @@
       
       MockSSOValve localValve = new MockSSOValve();
       localValve.setClusterManager(localSSOManager);
-      localSSOManager.setSingleSignOnValve(localValve);
+      localSSOManager.setSSOLocalManager(localValve);
       localSSOManager.start();
       
       
@@ -528,15 +529,15 @@
       
       Manager localSessMgr1 = getSessionManager("A");
       Session sess1 = new MockSession(localSessMgr1, "1");
-      localSSOManager.addSession("1", sess1);
+      localSSOManager.addSession("1", new FullyQualifiedSessionId(sess1));
       
       Manager localSessMgr2 = getSessionManager("B");
       Session sess2 = new MockSession(localSessMgr2, "1");
-      localSSOManager.addSession("1", sess2);
+      localSSOManager.addSession("1", new FullyQualifiedSessionId(sess2));
       
       assertEquals(2, localSSOManager.getSessionCount("1"));
       
-      localSSOManager.removeSession("1", sess2);
+      localSSOManager.removeSession("1", new FullyQualifiedSessionId(sess2));
       
       assertEquals(1, localSSOManager.getSessionCount("1"));
    }
@@ -607,13 +608,13 @@
    static class MockSSOValve extends ClusteredSingleSignOn
    {
       @Override
-      protected void notifySSOEmpty(String ssoId)
+      public void notifySSOEmpty(String ssoId)
       {
          // no-op
       }
 
       @Override
-      protected void notifySSONotEmpty(String ssoId)
+      public void notifySSONotEmpty(String ssoId)
       {
          // no-op
       }      

Modified: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/ClusteredSingleSignOn.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/ClusteredSingleSignOn.java	2008-08-25 19:26:41 UTC (rev 77452)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/ClusteredSingleSignOn.java	2008-08-25 20:32:27 UTC (rev 77453)
@@ -42,6 +42,11 @@
 import org.apache.catalina.connector.Response;
 import org.apache.catalina.session.ManagerBase;
 import org.jboss.web.tomcat.service.session.JBossManager;
+import org.jboss.web.tomcat.service.sso.jbcintegration.impl.TreeCacheSSOClusterManager;
+import org.jboss.web.tomcat.service.sso.jbcintegration.spi.FullyQualifiedSessionId;
+import org.jboss.web.tomcat.service.sso.jbcintegration.spi.SSOClusterManager;
+import org.jboss.web.tomcat.service.sso.jbcintegration.spi.SSOCredentials;
+import org.jboss.web.tomcat.service.sso.jbcintegration.spi.SSOLocalManager;
 
 
 /**
@@ -66,7 +71,7 @@
  */
 public class ClusteredSingleSignOn
    extends org.apache.catalina.authenticator.SingleSignOn
-   implements LifecycleListener
+   implements LifecycleListener, SSOLocalManager
 {
    /** By default we process expired SSOs no more often than once per minute */
    public static final int DEFAULT_PROCESS_EXPIRES_INTERVAL = 60;
@@ -407,7 +412,19 @@
 
       if (ssoClusterManager != null)
       {
-         ssoClusterManager.start();
+         try
+         {
+            ssoClusterManager.start();
+         }
+         catch (LifecycleException e)
+         {
+            throw e;
+         }
+         catch (Exception e)
+         {
+            throw new LifecycleException("Caught exception stopping " + 
+                  ssoClusterManager.getClass().getSimpleName(), e);
+         }
       }
 
    }
@@ -432,7 +449,19 @@
 
       if (ssoClusterManager != null)
       {
-         ssoClusterManager.stop();
+         try
+         {
+            ssoClusterManager.stop();
+         }
+         catch (LifecycleException e)
+         {
+            throw e;
+         }
+         catch (Exception e)
+         {
+            throw new LifecycleException("Caught exception stopping " + 
+                  ssoClusterManager.getClass().getSimpleName(), e);
+         }
       }
 
       lifecycle.fireLifecycleEvent(STOP_EVENT, null);
@@ -741,7 +770,7 @@
          }
          
          if (ssoClusterManager != null)         
-            ssoClusterManager.addSession(ssoId, session);
+            ssoClusterManager.addSession(ssoId, new FullyQualifiedSessionId(session));
       }
    }
 
@@ -771,7 +800,7 @@
       // If we changed anything, notify any cluster
       if (removed && ssoClusterManager != null)
       {
-         ssoClusterManager.removeSession(ssoId, session);
+         ssoClusterManager.removeSession(ssoId, new FullyQualifiedSessionId(session));
       }
 
       // see if this was the last session on this node, 
@@ -856,14 +885,17 @@
     */
    protected JBossSingleSignOnEntry getSingleSignOnEntry(String ssoId)
    {
-	   JBossSingleSignOnEntry sso = localLookup(ssoId);
+	  JBossSingleSignOnEntry sso = localLookup(ssoId);
       // If we don't have one locally and there is a cluster,
       // query the cluster for the SSO
       if (sso == null && ssoClusterManager != null)
       {
-         sso = ssoClusterManager.lookup(ssoId);
-         if (sso != null)
+         SSOCredentials credentials = ssoClusterManager.lookup(ssoId);
+         if (credentials != null)
          {
+            sso = new JBossSingleSignOnEntry(null, credentials.getAuthType(), 
+                                             credentials.getUsername(), 
+                                             credentials.getPassword());
             // Store it locally
             synchronized (cache)
             {
@@ -980,7 +1012,7 @@
       // If we changed anything, notify any cluster
       if (removed && ssoClusterManager != null)
       {
-         ssoClusterManager.removeSession(ssoId, session);
+         ssoClusterManager.removeSession(ssoId, new FullyQualifiedSessionId(session));
       }
 
       // Remove the inactive session from the 'reverse' Map.
@@ -1132,21 +1164,23 @@
 
    }
 
-   void remoteUpdate(String ssoId, String authType,
-      String username, String password)
+   public void remoteUpdate(String ssoId, SSOCredentials credentials)
    {
-	  JBossSingleSignOnEntry sso = getSingleSignOnEntry(ssoId);
+	  JBossSingleSignOnEntry sso = localLookup(ssoId);
       // Only update if the entry is missing information
       if (sso != null && sso.getCanReauthenticate() == false)
       {
          if (getContainer().getLogger().isTraceEnabled())
-             getContainer().getLogger().trace("Update sso id " + ssoId + " to auth type " + authType);
+             getContainer().getLogger().trace("Update sso id " + ssoId + 
+                   " to auth type " + credentials.getAuthType());
 
          synchronized (sso)
          {
             // Use the existing principal
             Principal p = sso.getPrincipal();
-            sso.updateCredentials(p, authType, username, password);
+            sso.updateCredentials(p, credentials.getAuthType(), 
+                                  credentials.getUsername(), 
+                                  credentials.getPassword());
          }
       }
 
@@ -1156,7 +1190,7 @@
     * Callback from the SSOManager when it detects an SSO without
     * any active sessions across the cluster
     */
-   protected void notifySSOEmpty(String ssoId)
+   public void notifySSOEmpty(String ssoId)
    {
       Object obj = emptySSOs.put(ssoId, new Long(System.currentTimeMillis()));
       
@@ -1170,7 +1204,7 @@
     * Callback from the SSOManager when it detects an SSO that
     * has active sessions across the cluster
     */
-   protected void notifySSONotEmpty(String ssoId)
+   public void notifySSONotEmpty(String ssoId)
    {
       Object obj = emptySSOs.remove(ssoId);
       
@@ -1214,7 +1248,7 @@
                Thread.currentThread().getContextClassLoader();
             Class clazz = tcl.loadClass(className);
             mgr = (SSOClusterManager) clazz.newInstance();
-            mgr.setSingleSignOnValve(this);
+            mgr.setSSOLocalManager(this);
             if (mgr instanceof TreeCacheSSOClusterManager)
             {
                ((TreeCacheSSOClusterManager) mgr).setCacheName(getTreeCacheName());
@@ -1232,7 +1266,19 @@
 
          if (started)
          {
-            ssoClusterManager.start();
+            try
+            {
+               ssoClusterManager.start();
+            }
+            catch (LifecycleException e)
+            {
+               throw e;
+            }
+            catch (Exception e)
+            {
+               throw new LifecycleException("Caught exception stopping " + 
+                     ssoClusterManager.getClass().getSimpleName(), e);
+            }
          }
       }
    }

Deleted: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/SSOClusterManager.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/SSOClusterManager.java	2008-08-25 19:26:41 UTC (rev 77452)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/SSOClusterManager.java	2008-08-25 20:32:27 UTC (rev 77453)
@@ -1,121 +0,0 @@
-/*
-* 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.sso;
-
-import org.apache.catalina.Session;
-import org.apache.catalina.Lifecycle;
-
-/**
- * Provides communications support between a SingleSignOn valve and other
- * such valves configured for the same hostname within a server cluster.
- * <p/>
- * Implementations of this interface must declare a public no-arguments
- * constructor.
- *
- * @author Brian E. Stansberry
- * @version $Revision: 45726 $ $Date: 2006-06-21 21:50:00 +0200 (mer., 21 juin 2006) $
- * @see ClusteredSingleSignOn
- */
-public interface SSOClusterManager
-   extends Lifecycle
-{
-
-   /**
-    * Notify the cluster of the addition of a Session to an SSO session.
-    *
-    * @param ssoId   the id of the SSO session
-    * @param session the Session that has been added
-    */
-   void addSession(String ssoId, Session session);
-
-   /**
-    * Gets the SingleSignOn valve for which this object is handling
-    * cluster communications.
-    *
-    * @return the <code>SingleSignOn</code> valve.
-    */
-   ClusteredSingleSignOn getSingleSignOnValve();
-
-   /**
-    * 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 valve a <code>SingleSignOn</code> valve.
-    */
-   void setSingleSignOnValve(ClusteredSingleSignOn valve);
-
-   /**
-    * Notifies the cluster that a single sign on session has been terminated
-    * due to a user logout.
-    *
-    * @param ssoId the id of the SSO session
-    */
-   void logout(String ssoId);
-
-   /**
-    * Queries the cluster for the existence of a SSO session with the given
-    * id, returning a <code>SingleSignOnEntry</code> if one is found.
-    *
-    * @param ssoId the id of the SSO session
-    * @return a <code>SingleSignOnEntry</code> created using information
-    *         found on another cluster node, or <code>null</code> if no
-    *         entry could be found.
-    */
-   JBossSingleSignOnEntry lookup(String ssoId);
-
-   /**
-    * 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
-    */
-   void register(String ssoId, String authType, String username,
-      String password);
-
-   /**
-    * Notify the cluster of the removal of a Session from an SSO session.
-    *
-    * @param ssoId   the id of the SSO session
-    * @param session the Session that has been removed
-    */
-   void removeSession(String ssoId, Session session);
-
-   /**
-    * 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
-    */
-   void updateCredentials(String ssoId, String authType, String username,
-      String password);
-
-
-}

Deleted: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/TreeCacheSSOClusterManager.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/TreeCacheSSOClusterManager.java	2008-08-25 19:26:41 UTC (rev 77452)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/TreeCacheSSOClusterManager.java	2008-08-25 20:32:27 UTC (rev 77453)
@@ -1,1748 +0,0 @@
-/*
-* JBoss, Home of Professional Open Source
-* Copyright 2006, JBoss Inc., and individual contributors as indicated
-* by the @authors tag. See the copyright.txt in the distribution for a
-* full listing of individual contributors.
-*
-* This is free software; you can redistribute it and/or modify it
-* under the terms of the GNU Lesser General Public License as
-* published by the Free Software Foundation; either version 2.1 of
-* the License, or (at your option) any later version.
-*
-* This software is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-* Lesser General Public License for more details.
-*
-* You should have received a copy of the GNU Lesser General Public
-* License along with this software; if not, write to the Free
-* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
-* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-*/
-package org.jboss.web.tomcat.service.sso;
-
-import java.io.Serializable;
-import java.security.AccessController;
-import java.security.Principal;
-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.apache.catalina.Container;
-import org.apache.catalina.LifecycleException;
-import org.apache.catalina.LifecycleListener;
-import org.apache.catalina.Session;
-import org.apache.catalina.util.LifecycleSupport;
-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.mx.util.MBeanServerLocator;
-import org.jboss.util.NestedRuntimeException;
-import org.jboss.util.loading.ContextClassLoaderSwitcher;
-import org.jboss.util.threadpool.ThreadPool;
-
-/**
- * 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 TreeCacheSSOClusterManager
-   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";
-   
-   // -------------------------------------------------------  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 lifecycle event support for this component.
-    */
-   private LifecycleSupport lifecycle = new LifecycleSupport(this);
-
-   /**
-    * 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 ClusteredSingleSignOn 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 TreeCacheSSOClusterManager
-    */
-   public TreeCacheSSOClusterManager()
-   {
-      // 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()
-   {
-      return cacheName;
-   }
-
-   public void setCacheName(String configName)
-      throws Exception
-   {
-      if (configName == cacheName ||
-            (cacheName != null  && cacheName.equals(configName)))
-         return;
-
-      removeAsCacheListener();
-      this.tm = null;
-      
-      this.cacheName = configName;
-
-      if (false == isTreeCacheAvailable(true))
-      {
-         if (started)
-         {
-            logMissingCacheError();
-         }
-         else
-         {
-            // Just put an advice in the log
-            log.info("Cannot find TreeCache using " + cacheName + " -- tree" +
-               "CacheName must be set to point to a running TreeCache " +
-               "before ClusteredSingleSignOn can handle requests");
-         }
-      }
-   }
-   
-   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 session the Session that has been added
-    */
-   public void addSession(String ssoId, Session session)
-   {
-      if (ssoId == null || session == null)
-      {
-         return;
-      }
-
-      if (!checkTreeCacheAvailable())
-      {
-         return;
-      }
-
-      if (log.isTraceEnabled())
-      {
-         log.trace("addSession(): adding Session " + session.getId() +
-            " 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, new FullyQualifiedSessionId(session), null);
-      }
-      catch (Exception e)
-      {
-         try
-         {
-            if(doTx)
-               tm.setRollbackOnly();
-         }
-         catch (Exception ignored)
-         {
-         }
-         String sessId = (session == null ? "NULL" : session.getId());
-         log.error("caught exception adding session " + sessId +
-            " 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 ClusteredSingleSignOn getSingleSignOnValve()
-   {
-      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 valve a <code>SingleSignOn</code> valve.
-    */
-   public void setSingleSignOnValve(ClusteredSingleSignOn valve)
-   {
-      ssoValve = valve;
-   }
-
-
-   /**
-    * 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);
-      }
-   }
-
-
-   /**
-    * Queries the cluster for the existence of an SSO session with the given
-    * id, returning a <code>SingleSignOnEntry</code> if one is found.
-    *
-    * @param ssoId the id of the SSO session
-    * @return a <code>SingleSignOnEntry</code> created using information
-    *         found on another cluster node, or <code>null</code> if no
-    *         entry could be found.
-    */
-   public JBossSingleSignOnEntry lookup(String ssoId)
-   {
-      if (!checkTreeCacheAvailable())
-      {
-         return null;
-      }
-
-      JBossSingleSignOnEntry entry = null;
-      // Find the latest credential info from the cluster
-      Fqn fqn = getCredentialsFqn(ssoId);
-      try
-      {
-         
-         SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn, KEY);
-         if (data != null)
-         {
-            entry = new JBossSingleSignOnEntry(null,
-               data.getAuthType(),
-               data.getUsername(),
-               data.getPassword());
-         }
-      }
-      catch (Exception e)
-      {
-         log.error("caught exception looking up SSOCredentials for SSO id " +
-            ssoId, e);
-      }
-      return entry;
-   }
-
-
-   /**
-    * 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 session the Session that has been removed
-    */
-   public void removeSession(String ssoId, Session session)
-   {
-      if (ssoId == null || session == 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 " + session.getId() +
-            " 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();
-
-         FullyQualifiedSessionId fqsi = new FullyQualifiedSessionId(session);
-         Set keys = getSessionKeys(ssoId);
-         if (keys.contains(fqsi))
-         {
-            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, fqsi);
-            }
-         }
-      }
-      catch (Exception e)
-      {
-         try
-         {
-            if(doTx)
-               tm.setRollbackOnly();
-         }
-         catch (Exception x)
-         {
-         }
-         
-         String sessId = (session == null ? "NULL" : session.getId());
-         log.error("caught exception removing session " + sessId +
-            " 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;
-      }
-
-      JBossSingleSignOnEntry sso = ssoValve.localLookup(ssoId);
-      if (sso == null || sso.getCanReauthenticate())
-      {
-         // No reason to update
-         return;
-      }
-
-      if (log.isTraceEnabled())
-      {
-         log.trace("received a credentials modified message for SSO " + ssoId);
-      }
-
-      try
-      {
-         SSOCredentials data = (SSOCredentials) nodeData.get(KEY);
-         if (data != null)
-         {
-            String authType = data.getAuthType();
-            String username = data.getUsername();
-            String password = data.getPassword();
-
-            if (log.isTraceEnabled())
-            {
-               log.trace("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 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));
-   }
-
-   
-   // -------------------------------------------------------------  Lifecycle
-
-
-   /**
-    * Add a lifecycle event listener to this component.
-    *
-    * @param listener The listener to add
-    */
-   public void addLifecycleListener(LifecycleListener listener)
-   {
-      lifecycle.addLifecycleListener(listener);
-   }
-
-
-   /**
-    * Get the lifecycle listeners associated with this lifecycle. If this
-    * Lifecycle has no listeners registered, a zero-length array is returned.
-    */
-   public LifecycleListener[] findLifecycleListeners()
-   {
-      return lifecycle.findLifecycleListeners();
-   }
-
-
-   /**
-    * Remove a lifecycle event listener from this component.
-    *
-    * @param listener The listener to remove
-    */
-   public void removeLifecycleListener(LifecycleListener listener)
-   {
-      lifecycle.removeLifecycleListener(listener);
-   }
-
-   /**
-    * 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 LifecycleException
-   {
-      // Validate and update our current component state
-      if (started)
-      {
-         throw new LifecycleException
-            ("TreeCacheSSOClusterManager already Started");
-      }
-      
-      initThreadPool();
-
-      try 
-      {
-         if (isTreeCacheAvailable(true))
-         {
-            integrateWithCache();
-         }
-      }
-      catch (Exception e)
-      {
-         // LifecycleException doesn't use normal exception chaining, so
-         // log the real problem here so we can sii what it is
-         log.error("Caught exception integrating with JBoss Cache", e);
-         throw new LifecycleException("Caught exception integrating with JBoss Cache", e);
-      }
-      
-      started = true;
-
-      // Notify our interested LifecycleListeners
-      lifecycle.fireLifecycleEvent(START_EVENT, null);
-   }
-
-
-   /**
-    * 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 LifecycleException
-   {
-      // Validate and update our current component state
-      if (!started)
-      {
-         throw new LifecycleException
-            ("TreeCacheSSOClusterManager not Started");
-      }
-      
-      started = false;
-
-      // Notify our interested LifecycleListeners
-      lifecycle.fireLifecycleEvent(STOP_EVENT, null);
-   }
-
-   
-   // -------------------------------------------------------  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 = new Fqn(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 new Fqn(objs);
-   }
-
-   private Fqn getSessionRootFqn(String ssoId)
-   {
-      Object[] objs = new Object[]{SSO, ssoId, SESSIONS };
-      return new Fqn(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 new Fqn(objs);
-   }
-
-   private Fqn getSingleSignOnFqn(String ssoid)
-   {
-      Object[] objs = new Object[]{SSO, ssoid};
-      return new Fqn(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("TreeCacheSSOClusterManager.endTransaction(): ", e);
-      }
-   }
-
-   /**
-    * 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 isTreeCacheAvailable(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 && server != null && cacheName.indexOf(':') > -1)
-            {
-               // See if there is a legacy JMX binding
-               String onameStr = cacheName;
-               if (ClusteredSingleSignOn.DEFAULT_CACHE_NAME.equals(cacheName))
-                  onameStr = ClusteredSingleSignOn.LEGACY_CACHE_NAME;
-               try
-               {
-                  ObjectName oname = new ObjectName(onameStr);
-                  Set s = server.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 = isTreeCacheAvailable(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) server.getAttribute(cacheObjectName, "PojoCache")).getCache();
-                     break;
-                  }
-                  else if ("Cache".equals(attr.getName()))
-                  {
-                     cache = (Cache) server.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 && server != 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
-
-   /**
-    * Private class used to store authentication credentials in the TreeCache.
-    * <p/>
-    * For security, password accessor is private.
-    */
-   public static class SSOCredentials
-      implements Serializable
-   {
-      /** The serialVersionUID */
-      private static final long serialVersionUID = 5704877226920571663L;
-      
-      private String authType = null;
-      private String password = null;
-      private String username = null;
-
-      /**
-       * Creates a new SSOCredentials.
-       *
-       * @param authType The authorization method used to authorize the
-       *                 SSO (BASIC, CLIENT-CERT, DIGEST, FORM or NONE).
-       * @param username The username of the user associated with the SSO
-       * @param password The password of the user associated with the SSO
-       */
-      private SSOCredentials(String authType, String username, String password)
-      {
-         this.authType = authType;
-         this.username = username;
-         this.password = password;
-      }
-
-      /**
-       * Gets the username of the user associated with the SSO.
-       *
-       * @return the username
-       */
-      public String getUsername()
-      {
-         return username;
-      }
-
-      /**
-       * Gets the authorization method used to authorize the SSO.
-       *
-       * @return "BASIC", "CLIENT-CERT", "DIGEST" or "FORM"
-       */
-      public String getAuthType()
-      {
-         return authType;
-      }
-
-      /**
-       * Gets the password of the user associated with the SSO.
-       *
-       * @return the password, or <code>null</code> if the authorization
-       *         type was DIGEST or CLIENT-CERT.
-       */
-      private String getPassword()
-      {
-         return password;
-      }
-
-   } // end SSOCredentials
-   
-   public static class FullyQualifiedSessionId implements Serializable
-   {
-      /** The serialVersionUID */
-      private static final long serialVersionUID = 6081884018218825708L;
-      
-      private final String hostName;
-      private final String contextName;
-      private final String sessionId;
-      
-      public FullyQualifiedSessionId(Session session)
-      {
-         this.sessionId = session.getIdInternal();
-         Container context = session.getManager().getContainer();
-         this.contextName = context.getName(); 
-         Container host = context.getParent();
-         this.hostName = host.getName();       
-      }
-      
-      /**
-       * Get the contextPath.
-       * 
-       * @return the contextPath.
-       */
-      public String getContextName()
-      {
-         return contextName;
-      }
-      /**
-       * Get the hostName.
-       * 
-       * @return the hostName.
-       */
-      public String getHostName()
-      {
-         return hostName;
-      }
-      /**
-       * Get the sessionId.
-       * 
-       * @return the sessionId.
-       */
-      public String getSessionId()
-      {
-         return sessionId;
-      }
-
-      @Override
-      public boolean equals(Object obj)
-      {
-         if (this == obj)
-            return true;
-         
-         if (obj instanceof FullyQualifiedSessionId)
-         {
-            FullyQualifiedSessionId other = (FullyQualifiedSessionId) obj;
-            return (hostName.equals(other.hostName) 
-                      && contextName.equals(other.contextName) 
-                      && sessionId.equals(other.sessionId));
-         }
-         
-         return false;
-      }
-
-      @Override
-      public int hashCode()
-      {
-         int result = 17;
-         result += 29 * hostName.hashCode();
-         result += 29 * contextName.hashCode();
-         result += 29 * sessionId.hashCode();
-         return result;
-      }
-
-      @Override
-      public String toString()
-      {
-         return hostName + "/" + contextName + "/" + sessionId;
-      }
-      
-   }
-
-   /**
-    * 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 TreeCacheSSOClusterManager
-

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/impl/TreeCacheSSOClusterManager.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/impl/TreeCacheSSOClusterManager.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/impl/TreeCacheSSOClusterManager.java	2008-08-25 20:32:27 UTC (rev 77453)
@@ -0,0 +1,1566 @@
+/*
+* 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.jbcintegration.impl;
+
+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.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.util.LifecycleSupport;
+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.mx.util.MBeanServerLocator;
+import org.jboss.util.NestedRuntimeException;
+import org.jboss.util.loading.ContextClassLoaderSwitcher;
+import org.jboss.util.threadpool.ThreadPool;
+import org.jboss.web.tomcat.service.sso.ClusteredSingleSignOn;
+import org.jboss.web.tomcat.service.sso.jbcintegration.spi.FullyQualifiedSessionId;
+import org.jboss.web.tomcat.service.sso.jbcintegration.spi.SSOClusterManager;
+import org.jboss.web.tomcat.service.sso.jbcintegration.spi.SSOCredentials;
+import org.jboss.web.tomcat.service.sso.jbcintegration.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 TreeCacheSSOClusterManager
+   implements SSOClusterManager, Lifecycle
+{
+   // -------------------------------------------------------------  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";
+   
+   // -------------------------------------------------------  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 lifecycle event support for this component.
+    */
+   private LifecycleSupport lifecycle = new LifecycleSupport(this);
+
+   /**
+    * 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 TreeCacheSSOClusterManager
+    */
+   public TreeCacheSSOClusterManager()
+   {
+      // 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()
+   {
+      return cacheName;
+   }
+
+   public void setCacheName(String configName)
+      throws Exception
+   {
+      if (configName == cacheName ||
+            (cacheName != null  && cacheName.equals(configName)))
+         return;
+
+      removeAsCacheListener();
+      this.tm = null;
+      
+      this.cacheName = configName;
+
+      if (false == isTreeCacheAvailable(true))
+      {
+         if (started)
+         {
+            logMissingCacheError();
+         }
+         else
+         {
+            // Just put an advice in the log
+            log.info("Cannot find TreeCache using " + cacheName + " -- tree" +
+               "CacheName must be set to point to a running TreeCache " +
+               "before ClusteredSingleSignOn can handle requests");
+         }
+      }
+   }
+   
+   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;
+   }
+
+
+   /**
+    * 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));
+   }
+
+   
+   // -------------------------------------------------------------  Lifecycle
+
+
+   /**
+    * Add a lifecycle event listener to this component.
+    *
+    * @param listener The listener to add
+    */
+   public void addLifecycleListener(LifecycleListener listener)
+   {
+      lifecycle.addLifecycleListener(listener);
+   }
+
+
+   /**
+    * Get the lifecycle listeners associated with this lifecycle. If this
+    * Lifecycle has no listeners registered, a zero-length array is returned.
+    */
+   public LifecycleListener[] findLifecycleListeners()
+   {
+      return lifecycle.findLifecycleListeners();
+   }
+
+
+   /**
+    * Remove a lifecycle event listener from this component.
+    *
+    * @param listener The listener to remove
+    */
+   public void removeLifecycleListener(LifecycleListener listener)
+   {
+      lifecycle.removeLifecycleListener(listener);
+   }
+
+   /**
+    * 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 LifecycleException
+   {
+      // Validate and update our current component state
+      if (started)
+      {
+         throw new LifecycleException
+            ("TreeCacheSSOClusterManager already Started");
+      }
+      
+      initThreadPool();
+
+      try 
+      {
+         if (isTreeCacheAvailable(true))
+         {
+            integrateWithCache();
+         }
+      }
+      catch (Exception e)
+      {
+         // LifecycleException doesn't use normal exception chaining, so
+         // log the real problem here so we can sii what it is
+         log.error("Caught exception integrating with JBoss Cache", e);
+         throw new LifecycleException("Caught exception integrating with JBoss Cache", e);
+      }
+      
+      started = true;
+
+      // Notify our interested LifecycleListeners
+      lifecycle.fireLifecycleEvent(START_EVENT, null);
+   }
+
+
+   /**
+    * 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 LifecycleException
+   {
+      // Validate and update our current component state
+      if (!started)
+      {
+         throw new LifecycleException
+            ("TreeCacheSSOClusterManager not Started");
+      }
+      
+      started = false;
+
+      // Notify our interested LifecycleListeners
+      lifecycle.fireLifecycleEvent(STOP_EVENT, null);
+   }
+
+   
+   // -------------------------------------------------------  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 = new Fqn(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 new Fqn(objs);
+   }
+
+   private Fqn getSessionRootFqn(String ssoId)
+   {
+      Object[] objs = new Object[]{SSO, ssoId, SESSIONS };
+      return new Fqn(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 new Fqn(objs);
+   }
+
+   private Fqn getSingleSignOnFqn(String ssoid)
+   {
+      Object[] objs = new Object[]{SSO, ssoid};
+      return new Fqn(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("TreeCacheSSOClusterManager.endTransaction(): ", e);
+      }
+   }
+
+   /**
+    * 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 isTreeCacheAvailable(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 && server != null && cacheName.indexOf(':') > -1)
+            {
+               // See if there is a legacy JMX binding
+               String onameStr = cacheName;
+               if (ClusteredSingleSignOn.DEFAULT_CACHE_NAME.equals(cacheName))
+                  onameStr = ClusteredSingleSignOn.LEGACY_CACHE_NAME;
+               try
+               {
+                  ObjectName oname = new ObjectName(onameStr);
+                  Set s = server.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 = isTreeCacheAvailable(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) server.getAttribute(cacheObjectName, "PojoCache")).getCache();
+                     break;
+                  }
+                  else if ("Cache".equals(attr.getName()))
+                  {
+                     cache = (Cache) server.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 && server != 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 TreeCacheSSOClusterManager
+


Property changes on: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/impl/TreeCacheSSOClusterManager.java
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/FullyQualifiedSessionId.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/FullyQualifiedSessionId.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/FullyQualifiedSessionId.java	2008-08-25 20:32:27 UTC (rev 77453)
@@ -0,0 +1,116 @@
+/*
+ * 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.sso.jbcintegration.spi;
+
+import java.io.Serializable;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Session;
+
+/**
+ * Encapsulates a session id along with the name of the owning context and
+ * its hostname.
+ * 
+ * @author <a href="brian.stansberry at jboss.com">Brian Stansberry</a>
+ * @version $Revision: 1 $
+ */
+public class FullyQualifiedSessionId implements Serializable
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 6081884018218825708L;
+   
+   private final String hostName;
+   private final String contextName;
+   private final String sessionId;
+   
+   public FullyQualifiedSessionId(Session session)
+   {
+      this.sessionId = session.getIdInternal();
+      Container context = session.getManager().getContainer();
+      this.contextName = context.getName(); 
+      Container host = context.getParent();
+      this.hostName = host.getName();       
+   }
+   
+   /**
+    * Get the contextPath.
+    * 
+    * @return the contextPath.
+    */
+   public String getContextName()
+   {
+      return contextName;
+   }
+   /**
+    * Get the hostName.
+    * 
+    * @return the hostName.
+    */
+   public String getHostName()
+   {
+      return hostName;
+   }
+   /**
+    * Get the sessionId.
+    * 
+    * @return the sessionId.
+    */
+   public String getSessionId()
+   {
+      return sessionId;
+   }
+
+   @Override
+   public boolean equals(Object obj)
+   {
+      if (this == obj)
+         return true;
+      
+      if (obj instanceof FullyQualifiedSessionId)
+      {
+         FullyQualifiedSessionId other = (FullyQualifiedSessionId) obj;
+         return (hostName.equals(other.hostName) 
+                   && contextName.equals(other.contextName) 
+                   && sessionId.equals(other.sessionId));
+      }
+      
+      return false;
+   }
+
+   @Override
+   public int hashCode()
+   {
+      int result = 17;
+      result += 29 * hostName.hashCode();
+      result += 29 * contextName.hashCode();
+      result += 29 * sessionId.hashCode();
+      return result;
+   }
+
+   @Override
+   public String toString()
+   {
+      return hostName + "/" + contextName + "/" + sessionId;
+   }
+   
+}
\ No newline at end of file


Property changes on: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/FullyQualifiedSessionId.java
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOClusterManager.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOClusterManager.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOClusterManager.java	2008-08-25 20:32:27 UTC (rev 77453)
@@ -0,0 +1,138 @@
+/*
+ * 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.sso.jbcintegration.spi;
+
+/**
+ * Provides communications support between a SingleSignOn valve and other
+ * such valves configured for the same hostname within a server cluster.
+ * <p/>
+ * Implementations of this interface must declare a public no-arguments
+ * constructor.
+ *
+ * @author Brian E. Stansberry
+ * @version $Revision: 45726 $ $Date: 2006-06-21 21:50:00 +0200 (mer., 21 juin 2006) $
+ * @see SSOLocalManager
+ */
+public interface 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
+    */
+   void addSession(String ssoId, FullyQualifiedSessionId sessionId);
+
+   /**
+    * Gets the SSOLocalManager valve for which this object is handling
+    * cluster communications.
+    *
+    * @return the <code>SSOLocalManager</code> valve.
+    */
+   SSOLocalManager getSSOLocalManager();
+
+   /**
+    * Sets the SSOLocalManager 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>SSOLocalManager</code> valve.
+    */
+   void setSSOLocalManager(SSOLocalManager localManager);
+
+   /**
+    * Notifies the cluster that a single sign on session has been terminated
+    * due to a user logout.
+    *
+    * @param ssoId the id of the SSO session
+    */
+   void logout(String ssoId);
+
+   /**
+    * Queries the cluster for the existence of a SSO session with the given
+    * id, returning a <code>SSOCredentials</code> if one is found.
+    *
+    * @param ssoId the id of the SSO session
+    * @return a <code>SSOCredentials</code> created using information
+    *         found on another cluster node, or <code>null</code> if no
+    *         entry could be found.
+    */
+   SSOCredentials lookup(String ssoId);
+
+   /**
+    * 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
+    */
+   void register(String ssoId, String authType, String username,
+      String 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
+    */
+   void removeSession(String ssoId, FullyQualifiedSessionId sessionId);
+
+   /**
+    * 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
+    */
+   void updateCredentials(String ssoId, String authType, String username,
+      String password);
+
+
+   /**
+    * 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.
+    *
+    * @exception Exception if this component detects a fatal error
+    *  that prevents this component from being used
+    */
+   public void start() throws Exception;
+
+
+   /**
+    * 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.
+    *
+    * @exception Exception if this component detects a fatal error
+    *  that needs to be reported
+    */
+   public void stop() throws Exception;
+}


Property changes on: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOClusterManager.java
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOCredentials.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOCredentials.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOCredentials.java	2008-08-25 20:32:27 UTC (rev 77453)
@@ -0,0 +1,89 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.sso.jbcintegration.spi;
+
+import java.io.Serializable;
+
+/**
+ * Authentication credentials for an SSO.
+ *
+ * @author Brian E. Stansberry
+ * @version $Revision: $
+ */
+public class SSOCredentials
+   implements Serializable
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 5704877226920571663L;
+   
+   private final String authType;
+   private final String password;
+   private final String username;
+
+   /**
+    * Creates a new SSOCredentials.
+    *
+    * @param authType The authorization method used to authorize the
+    *                 SSO (BASIC, CLIENT-CERT, DIGEST, FORM or NONE).
+    * @param username The username of the user associated with the SSO
+    * @param password The password of the user associated with the SSO
+    */
+   public SSOCredentials(String authType, String username, String password)
+   {
+      this.authType = authType;
+      this.username = username;
+      this.password = password;
+   }
+
+   /**
+    * Gets the username of the user associated with the SSO.
+    *
+    * @return the username
+    */
+   public String getUsername()
+   {
+      return username;
+   }
+
+   /**
+    * Gets the authorization method used to authorize the SSO.
+    *
+    * @return "BASIC", "CLIENT-CERT", "DIGEST" or "FORM"
+    */
+   public String getAuthType()
+   {
+      return authType;
+   }
+
+   /**
+    * Gets the password of the user associated with the SSO.
+    *
+    * @return the password, or <code>null</code> if the authorization
+    *         type was DIGEST or CLIENT-CERT.
+    */
+   public String getPassword()
+   {
+      return password;
+   }
+
+}
\ No newline at end of file


Property changes on: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOCredentials.java
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOLocalManager.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOLocalManager.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOLocalManager.java	2008-08-25 20:32:27 UTC (rev 77453)
@@ -0,0 +1,63 @@
+/*
+ * 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.sso.jbcintegration.spi;
+
+/**
+ * Interface implemented by the ClusteredSingleSignOn valve to allow
+ * callbacks by the {@link SSOClusterManager}.
+ * 
+ * @author <a href="brian.stansberry at jboss.com">Brian Stansberry</a>
+ * @version $Revision: 1 $
+ */
+public interface SSOLocalManager
+{
+   /**
+    * Callback from the SSOClusterManager notifying the local manager that
+    * an SSO has been deregistered on another node.
+    *
+    * @param ssoId Single sign on identifier to deregister
+    */
+   void deregister(String ssoId);
+
+   /**
+    * Callback from the SSOClusterManager notifying the local manager that
+    * the credentials associated with an SSO have been modified on another node.
+    * 
+    * @param ssoId  the id of the SSO
+    * @param credentials the updated credentials
+    */
+   void remoteUpdate(String ssoId, SSOCredentials credentials);
+
+   /**
+    * Callback from the SSOClusterManager when it detects an SSO without
+    * any active sessions across the cluster
+    */
+   void notifySSOEmpty(String ssoId);
+
+   /**
+    * Callback from the SSOClusterManager when it detects an SSO that
+    * has active sessions across the cluster
+    */
+   void notifySSONotEmpty(String ssoId);
+
+}
\ No newline at end of file


Property changes on: trunk/tomcat/src/main/org/jboss/web/tomcat/service/sso/jbcintegration/spi/SSOLocalManager.java
___________________________________________________________________
Name: svn:executable
   + *




More information about the jboss-cvs-commits mailing list