[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