KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > web > tomcat > tc6 > sso > TreeCacheSSOClusterManager


1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2006, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */

22 package org.jboss.web.tomcat.tc6.sso;
23
24 import java.io.Serializable JavaDoc;
25 import java.security.Principal JavaDoc;
26 import java.util.HashSet JavaDoc;
27 import java.util.Set JavaDoc;
28
29 import javax.management.MBeanServer JavaDoc;
30 import javax.management.ObjectName JavaDoc;
31 import javax.transaction.Status JavaDoc;
32 import javax.transaction.TransactionManager JavaDoc;
33
34 import org.apache.catalina.LifecycleException;
35 import org.apache.catalina.LifecycleListener;
36 import org.apache.catalina.Session;
37 import org.apache.catalina.util.LifecycleSupport;
38 import org.jboss.cache.AbstractCacheListener;
39 import org.jboss.cache.Cache;
40 import org.jboss.cache.Fqn;
41 import org.jboss.cache.InvocationContext;
42 import org.jboss.cache.Region;
43 import org.jboss.cache.RegionNotEmptyException;
44 import org.jboss.cache.config.Option;
45 import org.jboss.cache.jmx.CacheJmxWrapperMBean;
46 import org.jboss.logging.Logger;
47 import org.jboss.mx.util.MBeanProxyExt;
48 import org.jboss.mx.util.MBeanServerLocator;
49 import org.jboss.util.NestedRuntimeException;
50
51 /**
52  * An implementation of SSOClusterManager that uses a TreeCache
53  * to share SSO information between cluster nodes.
54  *
55  * @author Brian E. Stansberry
56  * @version $Revision: 58580 $ $Date: 2006-11-18 06:39:41 -0500 (Sat, 18 Nov 2006) $
57  */

58 public final class TreeCacheSSOClusterManager
59    extends AbstractCacheListener
60    implements SSOClusterManager
61 {
62    // ------------------------------------------------------------- Constants
63

64    /**
65     * Final segment of any FQN that names a TreeCache node storing
66     * SSO credential information.
67     */

68    private static final String JavaDoc CREDENTIALS = "credentials";
69
70    /**
71     * First segment of any FQN that names a TreeCache node associated
72     * with an SSO
73     */

74    private static final String JavaDoc SSO = "SSO";
75
76    /**
77     * Final segment of any FQN that names a TreeCache node storing
78     * the set of Sessions associated with an SSO.
79     */

80    private static final String JavaDoc SESSIONS = "sessions";
81
82    /**
83     * Key under which data is stored to the TreeCache.
84     */

85    private static final String JavaDoc KEY = "key";
86
87    /**
88     * Default global value for the cacheName property
89     */

90    public static final String JavaDoc DEFAULT_GLOBAL_CACHE_NAME =
91       "jboss.cache:service=TomcatClusteringCache";
92
93    private static final Option GRAVITATE_OPTION = new Option();
94
95    static
96    {
97       GRAVITATE_OPTION.setForceDataGravitation(true);
98    }
99    
100    // ------------------------------------------------------- Instance Fields
101

102    /**
103     * SSO id which the thread is currently storing to the cache
104     */

105    private ThreadLocal JavaDoc beingLocallyAdded = new ThreadLocal JavaDoc();
106
107    /**
108     * SSO id which a thread is currently removing from the cache
109     */

110    private ThreadLocal JavaDoc beingLocallyRemoved = new ThreadLocal JavaDoc();
111
112    /**
113     * SSO id which the thread is deregistering due to removal on another node
114     */

115    private ThreadLocal JavaDoc beingRemotelyRemoved = new ThreadLocal JavaDoc();
116
117    /**
118     * ObjectName of the TreeCache
119     */

120    private ObjectName JavaDoc cacheObjectName = null;
121
122    /**
123     * String version of the object name to use to access the TreeCache
124     */

125    private String JavaDoc cacheName = null;
126    
127    /**
128     * The cache itself.
129     */

130    private Cache cache = null;
131
132    /**
133     * Transaction Manager
134     */

135    private TransactionManager JavaDoc tm = null;
136    
137    /**
138     * The lifecycle event support for this component.
139     */

140    private LifecycleSupport lifecycle = new LifecycleSupport(this);
141
142    /**
143     * The Log-object for this class
144     */

145    private Logger log = Logger.getLogger(getClass().getName());;
146
147    /**
148     * Whether we are registered as a TreeCacheListener anywhere
149     */

150    private boolean registeredAsListener = false;
151
152    /**
153     * The MBean server we use to access our TreeCache
154     */

155    private MBeanServer JavaDoc server = null;
156
157    /**
158     * The SingleSignOn for which we are providing cluster support
159     */

160    private ClusteredSingleSignOn ssoValve = null;
161
162    /**
163     * Whether we have been started
164     */

165    private boolean started = false;
166
167    /**
168     * Whether a valid TreeCache is available for use
169     */

170    private boolean treeCacheAvailable = false;
171
172    /**
173     * Whether we have logged an error due to not having a valid cache
174     */

175    private boolean missingCacheErrorLogged = false;
176    
177    /**
178     * Our node's address in the cluster.
179     */

180    private Serializable JavaDoc localAddress = null;
181    
182    // ---------------------------------------------------------- Constructors
183

184    
185    /**
186     * Creates a new TreeCacheSSOClusterManager
187     */

188    public TreeCacheSSOClusterManager()
189    {
190       // Find our MBeanServer
191
server = MBeanServerLocator.locateJBoss();
192       if (server == null)
193          server = MBeanServerLocator.locate();
194    }
195
196    
197    /**
198     * Creates a new TreeCacheSSOClusterManager that works with the given
199     * MBeanServer. This constructor is only intended for use in unit testing.
200     */

201    public TreeCacheSSOClusterManager(MBeanServer JavaDoc server)
202    {
203       this.server = server;
204    }
205    
206    
207    // ------------------------------------------------------------ Properties
208

209    public String JavaDoc getCacheName()
210    {
211       return cacheName;
212    }
213
214    public void setCacheName(String JavaDoc objectName)
215       throws Exception JavaDoc
216    {
217       if (objectName == null)
218       {
219          setCacheObjectName(null);
220       }
221       else if (objectName.equals(cacheName) == false)
222       {
223          setCacheObjectName(new ObjectName JavaDoc(objectName));
224       }
225    }
226
227    public ObjectName JavaDoc getCacheObjectName()
228    {
229       return cacheObjectName;
230    }
231
232    public void setCacheObjectName(ObjectName JavaDoc objectName)
233       throws Exception JavaDoc
234    {
235       // If no change, do nothing
236
if ((objectName != null && objectName.equals(cacheObjectName))
237          || (cacheObjectName != null && cacheObjectName.equals(objectName))
238          || (objectName == null && cacheObjectName == null))
239       {
240          return;
241       }
242
243       removeAsCacheListener();
244       this.tm = null;
245       
246       this.cacheObjectName = objectName;
247       this.cacheName = (objectName == null
248          ? null
249          : objectName.getCanonicalName());
250
251       if (false == isTreeCacheAvailable(true))
252       {
253          if (started)
254          {
255             logMissingCacheError();
256          }
257          else
258          {
259             // Just put an advice in the log
260
log.info("Cannot find TreeCache using " + cacheName + " -- tree" +
261                "CacheName must be set to point to a running TreeCache " +
262                "before ClusteredSingleSignOn can handle requests");
263          }
264       }
265    }
266    
267    // ----------------------------------------------------- SSOClusterManager
268

269    /**
270     * Notify the cluster of the addition of a Session to an SSO session.
271     *
272     * @param ssoId the id of the SSO session
273     * @param session the Session that has been added
274     */

275    public void addSession(String JavaDoc ssoId, Session session)
276    {
277       if (ssoId == null || session == null)
278       {
279          return;
280       }
281
282       if (!checkTreeCacheAvailable())
283       {
284          return;
285       }
286
287       if (log.isTraceEnabled())
288       {
289          log.trace("addSession(): adding Session " + session.getId() +
290             " to cached session set for SSO " + ssoId);
291       }
292
293       Fqn fqn = getSessionsFqn(ssoId);
294       boolean doTx = false;
295       try
296       {
297          // Confirm we have a transaction manager; if not get it from TreeCache
298
// failure to find will throw an IllegalStateException
299
if (tm == null)
300             configureFromCache();
301          
302          // Don't do anything if there is already a transaction
303
// context associated with this thread.
304
if(tm.getTransaction() == null)
305             doTx = true;
306
307          if(doTx)
308             tm.begin();
309          
310          Set JavaDoc sessions = getSessionSet(fqn, true);
311          sessions.add(new SessionAddress(session.getId(), localAddress));
312          putInTreeCache(fqn, sessions);
313       }
314       catch (Exception JavaDoc e)
315       {
316          try
317          {
318             if(doTx)
319                tm.setRollbackOnly();
320          }
321          catch (Exception JavaDoc ignored)
322          {
323          }
324          String JavaDoc sessId = (session == null ? "NULL" : session.getId());
325          log.error("caught exception adding session " + sessId +
326             " to SSO id " + ssoId, e);
327       }
328       finally
329       {
330          if (doTx)
331             endTransaction();
332       }
333    }
334
335
336    /**
337     * Gets the SingleSignOn valve for which this object is handling
338     * cluster communications.
339     *
340     * @return the <code>SingleSignOn</code> valve.
341     */

342    public ClusteredSingleSignOn getSingleSignOnValve()
343    {
344       return ssoValve;
345    }
346
347
348    /**
349     * Sets the SingleSignOn valve for which this object is handling
350     * cluster communications.
351     * <p><b>NOTE:</b> This method must be called before calls can be
352     * made to the other methods of this interface.
353     *
354     * @param valve a <code>SingleSignOn</code> valve.
355     */

356    public void setSingleSignOnValve(ClusteredSingleSignOn valve)
357    {
358       ssoValve = valve;
359    }
360
361
362    /**
363     * Notifies the cluster that a single sign on session has been terminated
364     * due to a user logout.
365     *
366     * @param ssoId
367     */

368    public void logout(String JavaDoc ssoId)
369    {
370       if (!checkTreeCacheAvailable())
371       {
372          return;
373       }
374       
375       // Check whether we are already handling this removal
376
if (ssoId.equals(beingLocallyRemoved.get()))
377       {
378          return;
379       }
380       
381       // Add this SSO to our list of in-process local removals so
382
// this.nodeRemoved() will ignore the removal
383
beingLocallyRemoved.set(ssoId);
384
385       if (log.isTraceEnabled())
386       {
387          log.trace("Registering logout of SSO " + ssoId +
388             " in clustered cache");
389       }
390
391       Fqn fqn = getSingleSignOnFqn(ssoId);
392       
393       try
394       {
395          removeFromTreeCache(fqn);
396       }
397       catch (Exception JavaDoc e)
398       {
399          log.error("Exception attempting to remove node " +
400             fqn.toString() + " from TreeCache", e);
401       }
402       finally
403       {
404          beingLocallyRemoved.set(null);
405       }
406    }
407
408
409    /**
410     * Queries the cluster for the existence of an SSO session with the given
411     * id, returning a <code>SingleSignOnEntry</code> if one is found.
412     *
413     * @param ssoId the id of the SSO session
414     * @return a <code>SingleSignOnEntry</code> created using information
415     * found on another cluster node, or <code>null</code> if no
416     * entry could be found.
417     */

418    public SingleSignOnEntry lookup(String JavaDoc ssoId)
419    {
420       if (!checkTreeCacheAvailable())
421       {
422          return null;
423       }
424
425       SingleSignOnEntry entry = null;
426       // Find the latest credential info from the cluster
427
Fqn fqn = getCredentialsFqn(ssoId);
428       try
429       {
430          SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn);
431          if (data != null)
432          {
433             entry = new SingleSignOnEntry(null,
434                data.getAuthType(),
435                data.getUsername(),
436                data.getPassword());
437          }
438       }
439       catch (Exception JavaDoc e)
440       {
441          log.error("caught exception looking up SSOCredentials for SSO id " +
442             ssoId, e);
443       }
444       return entry;
445    }
446
447
448    /**
449     * Notifies the cluster of the creation of a new SSO entry.
450     *
451     * @param ssoId the id of the SSO session
452     * @param authType the type of authenticator (BASIC, CLIENT-CERT, DIGEST
453     * or FORM) used to authenticate the SSO.
454     * @param username the username (if any) used for the authentication
455     * @param password the password (if any) used for the authentication
456     */

457    public void register(String JavaDoc ssoId, String JavaDoc authType,
458       String JavaDoc username, String JavaDoc password)
459    {
460       if (!checkTreeCacheAvailable())
461       {
462          return;
463       }
464
465       if (log.isTraceEnabled())
466       {
467          log.trace("Registering SSO " + ssoId + " in clustered cache");
468       }
469
470       storeSSOData(ssoId, authType, username, password);
471    }
472
473
474    /**
475     * Notify the cluster of the removal of a Session from an SSO session.
476     *
477     * @param ssoId the id of the SSO session
478     * @param session the Session that has been removed
479     */

480    public void removeSession(String JavaDoc ssoId, Session session)
481    {
482       if (ssoId == null || session == null)
483       {
484          return;
485       }
486       
487       if (!checkTreeCacheAvailable())
488       {
489          return;
490       }
491       
492       // Check that this session removal is not due to our own deregistration
493
// of an SSO following receipt of a nodeRemoved() call
494
if (ssoId.equals(beingRemotelyRemoved.get()))
495       {
496          return;
497       }
498
499       if (log.isTraceEnabled())
500       {
501          log.trace("removeSession(): removing Session " + session.getId() +
502             " from cached session set for SSO " + ssoId);
503       }
504
505       Fqn fqn = getSessionsFqn(ssoId);
506       boolean doTx = false;
507       boolean removing = false;
508       try
509       {
510          // Confirm we have a transaction manager; if not get it from TreeCache
511
// failure to find will throw an IllegalStateException
512
if (tm == null)
513             configureFromCache();
514
515          // Don't do anything if there is already a transaction
516
// context associated with this thread.
517
if(tm.getTransaction() == null)
518             doTx = true;
519
520          if(doTx)
521             tm.begin();
522          
523          Set JavaDoc sessions = getSessionSet(fqn, false);
524          if (sessions != null)
525          {
526             sessions.remove(new SessionAddress(session.getId(), localAddress));
527             if (sessions.size() == 0)
528             {
529                // No sessions left; remove node
530

531                // Add this SSO to our list of in-process local removals so
532
// this.nodeRemoved() will ignore the removal
533
removing = true;
534                beingLocallyRemoved.set(ssoId);
535                removeFromTreeCache(getSingleSignOnFqn(ssoId));
536             }
537             else
538             {
539                putInTreeCache(fqn, sessions);
540             }
541          }
542       }
543       catch (Exception JavaDoc e)
544       {
545          try
546          {
547             if(doTx)
548                tm.setRollbackOnly();
549          }
550          catch (Exception JavaDoc x)
551          {
552          }
553          
554          String JavaDoc sessId = (session == null ? "NULL" : session.getId());
555          log.error("caught exception removing session " + sessId +
556             " from SSO id " + ssoId, e);
557       }
558       finally
559       {
560          try
561          {
562             if (removing)
563             {
564                beingLocallyRemoved.set(null);
565             }
566          }
567          finally
568          {
569             if (doTx)
570                endTransaction();
571          }
572       }
573    }
574
575
576    /**
577     * Notifies the cluster of an update of the security credentials
578     * associated with an SSO session.
579     *
580     * @param ssoId the id of the SSO session
581     * @param authType the type of authenticator (BASIC, CLIENT-CERT, DIGEST
582     * or FORM) used to authenticate the SSO.
583     * @param username the username (if any) used for the authentication
584     * @param password the password (if any) used for the authentication
585     */

586    public void updateCredentials(String JavaDoc ssoId, String JavaDoc authType,
587       String JavaDoc username, String JavaDoc password)
588    {
589       if (!checkTreeCacheAvailable())
590       {
591          return;
592       }
593
594       if (log.isTraceEnabled())
595       {
596          log.trace("Updating credentials for SSO " + ssoId +
597             " in clustered cache");
598       }
599
600       storeSSOData(ssoId, authType, username, password);
601    }
602
603    
604    // ------------------------------------------------------ CacheListener
605

606    /**
607     * Extracts an SSO session id from the Fqn and uses it in an invocation of
608     * {@link ClusteredSingleSignOn#deregister(String) ClusteredSingleSignOn.deregister(String)}.
609     * <p/>
610     * Ignores invocations resulting from TreeCache changes originated by
611     * this object.
612     *
613     * @param fqn the fully-qualified name of the node that was removed
614     */

615    public void nodeRemoved(Fqn fqn)
616    {
617       String JavaDoc ssoId = getIdFromFqn(fqn);
618       
619       if (ssoId == null)
620          return;
621
622       // Ignore messages generated by our own activity
623
if (ssoId.equals(beingLocallyRemoved.get()))
624       {
625          return;
626       }
627       
628       beingRemotelyRemoved.set(ssoId);
629
630       try
631       {
632          if (log.isTraceEnabled())
633          {
634             log.trace("received a node removed message for SSO " + ssoId);
635          }
636
637          ssoValve.deregister(ssoId);
638       }
639       finally
640       {
641          beingRemotelyRemoved.set(null);
642       }
643
644    }
645
646    /**
647     * Extracts an SSO session id from the Fqn and uses it in an invocation of
648     * {@link ClusteredSingleSignOn#update ClusteredSingleSignOn.update()}.
649     * <p/>
650     * Only responds to modifications of nodes whose FQN's final segment is
651     * "credentials".
652     * <p/>
653     * Ignores invocations resulting from TreeCache changes originated by
654     * this object.
655     * <p/>
656     * Ignores invocations for SSO session id's that are not registered
657     * with the local SingleSignOn valve.
658     *
659     * @param fqn the fully-qualified name of the node that was modified
660     */

661    public void nodeModified(Fqn fqn)
662    {
663       // We are only interested in changes to the CREDENTIALS node
664
if (CREDENTIALS.equals(getTypeFromFqn(fqn)) == false)
665       {
666          return;
667       }
668
669       String JavaDoc ssoId = getIdFromFqn(fqn); // won't be null or above check fails
670

671       // Ignore invocations that come as a result of our additions
672
if (ssoId.equals(beingLocallyAdded.get()))
673       {
674          return;
675       }
676
677       SingleSignOnEntry sso = ssoValve.localLookup(ssoId);
678       if (sso == null || sso.getCanReauthenticate())
679       {
680          // No reason to update
681
return;
682       }
683
684       if (log.isTraceEnabled())
685       {
686          log.trace("received a credentials modified message for SSO " + ssoId);
687       }
688
689       // Put this SSO in the queue of those to be updated
690
// credentialUpdater.enqueue(sso, ssoId);
691
try
692       {
693          SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn);
694          if (data != null)
695          {
696             // We want to release our read lock quickly, so get the needed
697
// data from the cache, commit the tx, and then use the data
698
String JavaDoc authType = data.getAuthType();
699             String JavaDoc username = data.getUsername();
700             String JavaDoc password = data.getPassword();
701
702             if (log.isTraceEnabled())
703             {
704                log.trace("CredentialUpdater: Updating credentials for SSO " + sso);
705             }
706
707             synchronized (sso)
708             {
709                // Use the existing principal
710
Principal JavaDoc p = sso.getPrincipal();
711                sso.updateCredentials(p, authType, username, password);
712             }
713          }
714       }
715       catch (Exception JavaDoc e)
716       {
717          log.error("failed to update credentials for SSO " + ssoId, e);
718       }
719    }
720
721    
722    // ------------------------------------------------------------- Lifecycle
723

724
725    /**
726     * Add a lifecycle event listener to this component.
727     *
728     * @param listener The listener to add
729     */

730    public void addLifecycleListener(LifecycleListener listener)
731    {
732       lifecycle.addLifecycleListener(listener);
733    }
734
735
736    /**
737     * Get the lifecycle listeners associated with this lifecycle. If this
738     * Lifecycle has no listeners registered, a zero-length array is returned.
739     */

740    public LifecycleListener[] findLifecycleListeners()
741    {
742       return lifecycle.findLifecycleListeners();
743    }
744
745
746    /**
747     * Remove a lifecycle event listener from this component.
748     *
749     * @param listener The listener to remove
750     */

751    public void removeLifecycleListener(LifecycleListener listener)
752    {
753       lifecycle.removeLifecycleListener(listener);
754    }
755
756    /**
757     * Prepare for the beginning of active use of the public methods of this
758     * component. This method should be called before any of the public
759     * methods of this component are utilized. It should also send a
760     * LifecycleEvent of type START_EVENT to any registered listeners.
761     *
762     * @throws LifecycleException if this component detects a fatal error
763     * that prevents this component from being used
764     */

765    public void start() throws LifecycleException
766    {
767       // Validate and update our current component state
768
if (started)
769       {
770          throw new LifecycleException
771             ("TreeCacheSSOClusterManager already Started");
772       }
773
774       try
775       {
776          if (isTreeCacheAvailable(true))
777          {
778             integrateWithCache();
779          }
780       }
781       catch (Exception JavaDoc e)
782       {
783          throw new LifecycleException("Caught exception looking up " +
784                                       "TransactionManager from TreeCache", e);
785       }
786       
787       started = true;
788
789       // Notify our interested LifecycleListeners
790
lifecycle.fireLifecycleEvent(START_EVENT, null);
791    }
792
793
794    /**
795     * Gracefully terminate the active use of the public methods of this
796     * component. This method should be the last one called on a given
797     * instance of this component. It should also send a LifecycleEvent
798     * of type STOP_EVENT to any registered listeners.
799     *
800     * @throws LifecycleException if this component detects a fatal error
801     * that needs to be reported
802     */

803    public void stop() throws LifecycleException
804    {
805       // Validate and update our current component state
806
if (!started)
807       {
808          throw new LifecycleException
809             ("TreeCacheSSOClusterManager not Started");
810       }
811       
812       started = false;
813
814       // Notify our interested LifecycleListeners
815
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
816    }
817
818    
819    // ------------------------------------------------------- Private Methods
820

821    private Object JavaDoc getFromTreeCache(Fqn fqn) throws Exception JavaDoc
822    {
823       InvocationContext ctx = cache.getInvocationContext();
824       Option existing = ctx.getOptionOverrides();
825       try
826       {
827          ctx.setOptionOverrides(GRAVITATE_OPTION);
828          return cache.get(fqn, KEY);
829       }
830       finally
831       {
832          ctx.setOptionOverrides(existing);
833       }
834    }
835
836    private Fqn getCredentialsFqn(String JavaDoc ssoid)
837    {
838       Object JavaDoc[] objs = new Object JavaDoc[]{SSO, ssoid, CREDENTIALS};
839       return new Fqn(objs);
840    }
841
842    private Fqn getSessionsFqn(String JavaDoc ssoid)
843    {
844       Object JavaDoc[] objs = new Object JavaDoc[]{SSO, ssoid, SESSIONS};
845       return new Fqn(objs);
846    }
847
848    private Fqn getSingleSignOnFqn(String JavaDoc ssoid)
849    {
850       Object JavaDoc[] objs = new Object JavaDoc[]{SSO, ssoid};
851       return new Fqn(objs);
852    }
853
854    /**
855     * Extracts an SSO session id from a fully qualified name object.
856     *
857     * @param fqn the Fully Qualified Name used by TreeCache
858     * @return the second element in the Fqn -- the SSO session id
859     */

860    private String JavaDoc getIdFromFqn(Fqn fqn)
861    {
862       String JavaDoc id = null;
863       if (fqn.size() > 1 && SSO.equals(fqn.get(0)))
864       {
865          id = (String JavaDoc) fqn.get(1);
866       }
867       return id;
868    }
869
870    private Set JavaDoc getSessionSet(Fqn fqn, boolean create)
871       throws Exception JavaDoc
872    {
873       Set JavaDoc sessions = (Set JavaDoc) getFromTreeCache(fqn);
874       if (create && sessions == null)
875       {
876          sessions = new HashSet JavaDoc();
877       }
878       return sessions;
879    }
880
881    /**
882     * Extracts the SSO tree cache node type from a fully qualified name
883     * object.
884     *
885     * @param fqn the Fully Qualified Name used by TreeCache
886     * @return the 3rd in the Fqn -- either
887     * {@link #CREDENTIALS CREDENTIALS} or {@link #SESSIONS SESSIONS},
888     * or <code>null</code> if <code>fqn</code> is not for an SSO.
889     */

890    private String JavaDoc getTypeFromFqn(Fqn fqn)
891    {
892       String JavaDoc type = null;
893       if (fqn.size() > 2 && SSO.equals(fqn.get(0)))
894          type = (String JavaDoc) fqn.get(2);
895       return type;
896    }
897    
898    /**
899     * Obtains needed configuration information from the tree cache.
900     * Invokes "getTransactionManager" on the tree cache, caching the
901     * result or throwing an IllegalStateException if one is not found.
902     * Also get our cluster-wide unique local address from the cache.
903     *
904     * @throws Exception
905     */

906    private void configureFromCache() throws Exception JavaDoc
907    {
908       tm = cache.getTransactionManager();
909
910       if (tm == null)
911       {
912          throw new IllegalStateException JavaDoc("Cache does not have a " +
913                                          "transaction manager; please " +
914                                          "configure a valid " +
915                                          "TransactionManagerLookupClass");
916       }
917       
918       // Find out our address
919
Object JavaDoc address = cache.getLocalAddress();
920       // In reality this is a JGroups IpAddress, but the API says
921
// "Object" so we have to be sure its Serializable
922
if (address instanceof Serializable JavaDoc)
923          localAddress = (Serializable JavaDoc) address;
924       else
925          localAddress = address.toString();
926    }
927
928    private void endTransaction()
929    {
930       try
931       {
932          if(tm.getTransaction().getStatus() != Status.STATUS_MARKED_ROLLBACK)
933          {
934             tm.commit();
935          }
936          else
937          {
938             tm.rollback();
939          }
940       }
941       catch (Exception JavaDoc e)
942       {
943          log.error(e);
944          throw new NestedRuntimeException("TreeCacheSSOClusterManager.endTransaction(): ", e);
945       }
946    }
947
948    /**
949     * Checks whether an MBean is registered under the value of property
950     * "cacheObjectName".
951     *
952     * @param forceCheck check for availability whether or not it has already
953     * been positively established
954     * @return <code>true</code> if property <code>cacheName</code> has been
955     * set and points to a registered MBean.
956     */

957    private synchronized boolean isTreeCacheAvailable(boolean forceCheck)
958    {
959       if (forceCheck || treeCacheAvailable == false)
960       {
961          boolean available = (cacheObjectName != null);
962          if (available)
963          {
964             Set JavaDoc s = server.queryMBeans(cacheObjectName, null);
965             available = s.size() > 0;
966             if (available)
967             {
968                try
969                {
970                   // If Tomcat6 overrides the default cache name, it will do so
971
// after we are started. So we need to configure ourself here
972
// and throw an exception if there is a problem. Having this
973
// here also allows us to recover if our cache is started
974
// after we are
975
if (started)
976                      integrateWithCache();
977                   setMissingCacheErrorLogged(false);
978                }
979                catch (Exception JavaDoc e)
980                {
981                   log.error("Caught exception configuring from cache " +
982                             cacheObjectName, e);
983                   available = false;
984                }
985             }
986          }
987          treeCacheAvailable = available;
988       }
989       return treeCacheAvailable;
990    }
991    
992    private boolean checkTreeCacheAvailable()
993    {
994       boolean avail = isTreeCacheAvailable(false);
995       if (!avail)
996          logMissingCacheError();
997       return avail;
998    }
999
1000   private void putInTreeCache(Fqn fqn, Object JavaDoc data) throws Exception JavaDoc
1001   {
1002      InvocationContext ctx = cache.getInvocationContext();
1003      Option existing = ctx.getOptionOverrides();
1004      try
1005      {
1006         ctx.setOptionOverrides(GRAVITATE_OPTION);
1007         cache.put(fqn, KEY, data);
1008      }
1009      finally
1010      {
1011         ctx.setOptionOverrides(existing);
1012      }
1013   }
1014
1015   private void integrateWithCache() throws Exception JavaDoc
1016   {
1017      if (cache == null)
1018      {
1019         // Get the cache
1020
CacheJmxWrapperMBean mbean = (CacheJmxWrapperMBean) MBeanProxyExt.create(CacheJmxWrapperMBean.class,
1021                                                           getCacheObjectName());
1022         cache = mbean.getCache();
1023         
1024         // Ensure we have a transaction manager and a cluster-wide unique address
1025
configureFromCache();
1026         
1027         // If the SSO region is inactive, activate it
1028
activateCacheRegion();
1029         
1030         registerAsCacheListener();
1031         
1032         log.debug("Successfully integrated with cache service " + cacheObjectName);
1033      }
1034   }
1035
1036
1037   /**
1038    * If we are sharing a cache with HttpSession replication, the SSO
1039    * region may not be active, so here we ensure it is.
1040    *
1041    * @throws Exception
1042    */

1043   private void activateCacheRegion() throws Exception JavaDoc
1044   {
1045      if (cache.getConfiguration().isInactiveOnStartup())
1046      {
1047         if (cache.getConfiguration().isUseRegionBasedMarshalling())
1048         {
1049            Region region =cache.getRegion(Fqn.fromString("/" + SSO), true);
1050            try
1051            {
1052               region.activate();
1053            }
1054            catch (RegionNotEmptyException e)
1055            {
1056               log.debug(SSO + " region already active", e);
1057            }
1058         }
1059      }
1060   }
1061
1062   /**
1063    * Invokes an operation on the JMX server to register ourself as a
1064    * listener on the TreeCache service.
1065    *
1066    * @throws Exception
1067    */

1068   private void registerAsCacheListener() throws Exception JavaDoc
1069   {
1070      cache.addCacheListener(this);
1071      registeredAsListener = true;
1072   }
1073
1074
1075   /**
1076    * Invokes an operation on the JMX server to register ourself as a
1077    * listener on the TreeCache service.
1078    *
1079    * @throws Exception
1080    */

1081   private void removeAsCacheListener() throws Exception JavaDoc
1082   {
1083      if (registeredAsListener && cache != null)
1084      {
1085         cache.removeCacheListener(this);
1086         registeredAsListener = false;
1087      }
1088   }
1089
1090   private void removeFromTreeCache(Fqn fqn) throws Exception JavaDoc
1091   {
1092      InvocationContext ctx = cache.getInvocationContext();
1093      Option existing = ctx.getOptionOverrides();
1094      try
1095      {
1096         ctx.setOptionOverrides(GRAVITATE_OPTION);
1097         cache.remove(fqn);
1098      }
1099      finally
1100      {
1101         ctx.setOptionOverrides(existing);
1102      }
1103   }
1104
1105   /**
1106    * Stores the given data to the clustered cache in a tree branch whose FQN
1107    * is the given SSO id. Stores the given credential data in a child node
1108    * named "credentials". If parameter <code>storeSessions</code> is
1109    * <code>true</code>, also stores an empty HashSet in a sibling node
1110    * named "sessions". This HashSet will later be used to hold session ids
1111    * associated with the SSO.
1112    * <p/>
1113    * Any items stored are stored under the key "key".
1114    *
1115    * @param ssoId the id of the SSO session
1116    * @param authType the type of authenticator (BASIC, CLIENT-CERT, DIGEST
1117    * or FORM) used to authenticate the SSO.
1118    * @param username the username (if any) used for the authentication
1119    * @param password the password (if any) used for the authentication
1120    */

1121   private void storeSSOData(String JavaDoc ssoId, String JavaDoc authType, String JavaDoc username,
1122      String JavaDoc password)
1123   {
1124      SSOCredentials data = new SSOCredentials(authType, username, password);
1125      
1126      // Add this SSO to our list of in-process local adds so
1127
// this.nodeModified() will ignore the addition
1128
beingLocallyAdded.set(ssoId);
1129      
1130      try
1131      {
1132         putInTreeCache(getCredentialsFqn(ssoId), data);
1133      }
1134      catch (Exception JavaDoc e)
1135      {
1136         log.error("Exception attempting to add TreeCache nodes for SSO " +
1137            ssoId, e);
1138      }
1139      finally
1140      {
1141         beingLocallyAdded.set(null);
1142      }
1143   }
1144
1145   private boolean isMissingCacheErrorLogged()
1146   {
1147      return missingCacheErrorLogged;
1148   }
1149
1150   private void setMissingCacheErrorLogged(boolean missingCacheErrorLogged)
1151   {
1152      this.missingCacheErrorLogged = missingCacheErrorLogged;
1153   }
1154
1155   private void logMissingCacheError()
1156   {
1157      StringBuffer JavaDoc msg = new StringBuffer JavaDoc("Cannot find TreeCache using ");
1158      msg.append(getCacheName());
1159      msg.append(" -- TreeCache must be started before ClusteredSingleSignOn ");
1160      msg.append("can handle requests");
1161
1162      if (isMissingCacheErrorLogged())
1163      {
1164         // Just log it as a warning
1165
log.warn(msg);
1166      }
1167      else
1168      {
1169         log.error(msg);
1170         // Set a flag so we don't relog this error over and over
1171
setMissingCacheErrorLogged(true);
1172      }
1173   }
1174
1175   // --------------------------------------------------------- Outer Classes
1176

1177   /**
1178    * Private class used to store authentication credentials in the TreeCache.
1179    * <p/>
1180    * For security, password accessor is private.
1181    */

1182   public static class SSOCredentials
1183      implements Serializable JavaDoc
1184   {
1185      /** The serialVersionUID */
1186      private static final long serialVersionUID = 5704877226920571663L;
1187      
1188      private String JavaDoc authType = null;
1189      private String JavaDoc password = null;
1190      private String JavaDoc username = null;
1191
1192      /**
1193       * Creates a new SSOCredentials.
1194       *
1195       * @param authType The authorization method used to authorize the
1196       * SSO (BASIC, CLIENT-CERT, DIGEST, FORM or NONE).
1197       * @param username The username of the user associated with the SSO
1198       * @param password The password of the user associated with the SSO
1199       */

1200      private SSOCredentials(String JavaDoc authType, String JavaDoc username, String JavaDoc password)
1201      {
1202         this.authType = authType;
1203         this.username = username;
1204         this.password = password;
1205      }
1206
1207      /**
1208       * Gets the username of the user associated with the SSO.
1209       *
1210       * @return the username
1211       */

1212      public String JavaDoc getUsername()
1213      {
1214         return username;
1215      }
1216
1217      /**
1218       * Gets the authorization method used to authorize the SSO.
1219       *
1220       * @return "BASIC", "CLIENT-CERT", "DIGEST" or "FORM"
1221       */

1222      public String JavaDoc getAuthType()
1223      {
1224         return authType;
1225      }
1226
1227      /**
1228       * Gets the password of the user associated with the SSO.
1229       *
1230       * @return the password, or <code>null</code> if the authorization
1231       * type was DIGEST or CLIENT-CERT.
1232       */

1233      private String JavaDoc getPassword()
1234      {
1235         return password;
1236      }
1237
1238   } // end SSOCredentials
1239

1240   static class SessionAddress implements Serializable JavaDoc
1241   {
1242      /** The serialVersionUID */
1243      private static final long serialVersionUID = -3702932999380140004L;
1244      
1245      Serializable JavaDoc address;
1246      String JavaDoc sessionId;
1247      
1248      SessionAddress(String JavaDoc sessionId, Serializable JavaDoc address)
1249      {
1250         this.sessionId = sessionId;
1251         this.address = address;
1252      }
1253
1254      public boolean equals(Object JavaDoc obj)
1255      {
1256         if (this == obj)
1257            return true;
1258         
1259         if (!(obj instanceof SessionAddress))
1260            return false;
1261         
1262         SessionAddress other = (SessionAddress) obj;
1263         
1264         return (sessionId.equals(other.sessionId)
1265                 && address.equals(other.address));
1266      }
1267
1268      public int hashCode()
1269      {
1270         int total = (19 * 43) + sessionId.hashCode();
1271         return ((total * 43) + address.hashCode());
1272      }
1273      
1274      
1275   }
1276
1277} // end TreeCacheSSOClusterManager
1278

1279
Popular Tags