KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * JBoss, the OpenSource WebOS
3  *
4  * Distributable under LGPL license.
5  * See terms of license at gnu.org.
6  */

7 package org.jboss.web.tomcat.tc5.sso;
8
9 import java.io.Serializable JavaDoc;
10 import java.security.Principal JavaDoc;
11 import java.util.HashSet JavaDoc;
12 import java.util.LinkedList JavaDoc;
13 import java.util.Set JavaDoc;
14
15 import javax.management.MBeanServer JavaDoc;
16 import javax.management.ObjectName JavaDoc;
17 import javax.naming.InitialContext JavaDoc;
18 import javax.naming.NamingException JavaDoc;
19 import javax.transaction.UserTransaction JavaDoc;
20
21 import org.apache.catalina.LifecycleException;
22 import org.apache.catalina.LifecycleListener;
23 import org.apache.catalina.Session;
24 import org.apache.catalina.util.LifecycleSupport;
25 import org.jboss.cache.Fqn;
26 import org.jboss.cache.TreeCache;
27 import org.jboss.cache.TreeCacheListener;
28 import org.jboss.logging.Logger;
29 import org.jboss.mx.util.MBeanServerLocator;
30 import org.jboss.web.tomcat.tc5.Tomcat5;
31 import org.jgroups.View;
32
33 /**
34  * An implementation of SSOClusterManager that uses a TreeCache
35  * to share SSO information between cluster nodes.
36  *
37  * @author Brian E. Stansberry
38  * @version $Revision& $Date: 2005/04/03 07:23:28 $
39  */

40 public final class TreeCacheSSOClusterManager
41    implements SSOClusterManager, TreeCacheListener
42 {
43    // ------------------------------------------------------------- Constants
44

45    /**
46     * Final segment of any FQN that names a TreeCache node storing
47     * SSO credential information.
48     */

49    private static final String JavaDoc CREDENTIALS = "credentials";
50
51    /**
52     * First segment of any FQN that names a TreeCache node associated
53     * with an SSO
54     */

55    private static final String JavaDoc SSO = "SSO";
56
57    /**
58     * Final segment of any FQN that names a TreeCache node storing
59     * the set of Sessions associated with an SSO.
60     */

61    private static final String JavaDoc SESSIONS = "sessions";
62
63    /**
64     * Key under which data is stored to the TreeCache.
65     */

66    private static final String JavaDoc KEY = "key";
67
68    /**
69     * Default global value for the cacheName property
70     */

71    public static final String JavaDoc DEFAULT_GLOBAL_CACHE_NAME =
72       Tomcat5.DEFAULT_CACHE_NAME;
73
74    /**
75     * Parameter signature used for TreeCache.get calls over JMX
76     */

77    private static final String JavaDoc[] GET_SIGNATURE =
78       {Fqn.class.getName(), Object JavaDoc.class.getName()};
79
80    /**
81     * Parameter signature used for TreeCache.put calls over JMX
82     */

83    private static final String JavaDoc[] PUT_SIGNATURE =
84       {Fqn.class.getName(), Object JavaDoc.class.getName(), Object JavaDoc.class.getName()};
85
86    /**
87     * Parameter signature used for TreeCache.remove calls over JMX
88     */

89    private static final String JavaDoc[] REMOVE_SIGNATURE = {Fqn.class.getName()};
90    
91    // ------------------------------------------------------- Instance Fields
92

93    /**
94     * List of SSO ids which this object is currently storing to the cache
95     */

96    private LinkedList JavaDoc beingLocallyAdded = new LinkedList JavaDoc();
97
98    /**
99     * List of SSO ids which this object is currently removing from the cache
100     */

101    private LinkedList JavaDoc beingLocallyRemoved = new LinkedList JavaDoc();
102
103    /**
104     * List of SSO ids which are being deregistered due to removal on another
105     * node
106     */

107    private LinkedList JavaDoc beingRemotelyRemoved = new LinkedList JavaDoc();
108
109    /**
110     * ObjectName of the TreeCache
111     */

112    private ObjectName JavaDoc cacheObjectName = null;
113
114    /**
115     * String version of the object name to use to access the TreeCache
116     */

117    private String JavaDoc cacheName = null;
118
119    /**
120     * CredentialUpdater used to allow asynchronous updates of
121     * SSO credentials
122     */

123    private CredentialUpdater credentialUpdater = null;
124
125    /**
126     * InitialContext used for JNDI lookups
127     */

128    private InitialContext JavaDoc initialContext = null;
129
130    /**
131     * The lifecycle event support for this component.
132     */

133    private LifecycleSupport lifecycle = new LifecycleSupport(this);
134
135    /**
136     * The Log-object for this class
137     */

138    private Logger log = Logger.getLogger(getClass().getName());;
139
140    /**
141     * Whether we are registered as a TreeCacheListener anywhere
142     */

143    private boolean registeredAsListener = false;
144
145    /**
146     * The MBean server we use to access our TreeCache
147     */

148    private MBeanServer JavaDoc server = null;
149
150    /**
151     * The SingleSignOn for which we are providing cluster support
152     */

153    private ClusteredSingleSignOn ssoValve = null;
154
155    /**
156     * Whether we have been started
157     */

158    private boolean started = false;
159
160    /**
161     * Whether a valid TreeCache is available for use
162     */

163    private boolean treeCacheAvailable = false;
164
165    /**
166     * Whether we have logged an error due to not having a valid cache
167     */

168    private boolean missingCacheErrorLogged = false;
169    
170    // ---------------------------------------------------------- Constructors
171

172    
173    /**
174     * Creates a new TreeCacheSSOClusterManager
175     */

176    public TreeCacheSSOClusterManager()
177    {
178       // Find our MBeanServer
179
server = MBeanServerLocator.locate();
180    }
181    
182    
183    // ------------------------------------------------------------ Properties
184

185    public String JavaDoc getCacheName()
186    {
187       return cacheName;
188    }
189
190    public void setCacheName(String JavaDoc objectName)
191       throws Exception JavaDoc
192    {
193       if (objectName == null)
194       {
195          setCacheObjectName(null);
196       }
197       else if (objectName.equals(cacheName) == false)
198       {
199          setCacheObjectName(new ObjectName JavaDoc(objectName));
200       }
201    }
202
203    public ObjectName JavaDoc getCacheObjectName()
204    {
205       return cacheObjectName;
206    }
207
208    public void setCacheObjectName(ObjectName JavaDoc objectName)
209       throws Exception JavaDoc
210    {
211       // If no change, do nothing
212
if ((objectName != null && objectName.equals(cacheObjectName))
213          || (cacheObjectName != null && cacheObjectName.equals(objectName))
214          || (objectName == null && cacheObjectName == null))
215       {
216          return;
217       }
218
219       removeAsTreeCacheListener(cacheObjectName);
220       this.cacheObjectName = objectName;
221       this.cacheName = (objectName == null
222          ? null
223          : objectName.getCanonicalName());
224
225       if (false == isTreeCacheAvailable(true))
226       {
227          if (started)
228          {
229             logMissingCacheError();
230          }
231          else
232          {
233             // Just put an advice in the log
234
log.info("Cannot find TreeCache using " + cacheName + " -- tree" +
235                "CacheName must be set to point to a running TreeCache " +
236                "before ClusteredSingleSignOn can handle requests");
237          }
238       }
239    }
240    
241    // ----------------------------------------------------- SSOClusterManager
242

243    /**
244     * Notify the cluster of the addition of a Session to an SSO session.
245     *
246     * @param ssoId the id of the SSO session
247     * @param session the Session that has been added
248     */

249    public void addSession(String JavaDoc ssoId, Session session)
250    {
251       if (ssoId == null || session == null)
252       {
253          return;
254       }
255
256       if (false == isTreeCacheAvailable(false))
257       {
258          logMissingCacheError();
259          return;
260       }
261
262       if (log.isTraceEnabled())
263       {
264          log.trace("addSession(): adding Session " + session.getId() +
265             " to cached session set for SSO " + ssoId);
266       }
267
268       Fqn fqn = getSessionsFqn(ssoId);
269       UserTransaction JavaDoc tx = null;
270       try
271       {
272          tx = getNewTransaction();
273          tx.begin();
274          Set JavaDoc sessions = getSessionSet(fqn, true);
275          sessions.add(session.getId());
276          putInTreeCache(fqn, sessions);
277          tx.commit();
278       }
279       catch (Exception JavaDoc e)
280       {
281          if (tx != null)
282          {
283             try
284             {
285                tx.rollback();
286             }
287             catch (Exception JavaDoc x)
288             {
289             }
290          }
291          String JavaDoc sessId = (session == null ? "NULL" : session.getId());
292          log.error("caught exception adding session " + sessId +
293             " to SSO id " + ssoId, e);
294       }
295    }
296
297
298    /**
299     * Gets the SingleSignOn valve for which this object is handling
300     * cluster communications.
301     *
302     * @return the <code>SingleSignOn</code> valve.
303     */

304    public ClusteredSingleSignOn getSingleSignOnValve()
305    {
306       return ssoValve;
307    }
308
309
310    /**
311     * Sets the SingleSignOn valve for which this object is handling
312     * cluster communications.
313     * <p><b>NOTE:</b> This method must be called before calls can be
314     * made to the other methods of this interface.
315     *
316     * @param valve a <code>SingleSignOn</code> valve.
317     */

318    public void setSingleSignOnValve(ClusteredSingleSignOn valve)
319    {
320       ssoValve = valve;
321    }
322
323
324    /**
325     * Notifies the cluster that a single sign on session has been terminated
326     * due to a user logout.
327     *
328     * @param ssoId
329     */

330    public void logout(String JavaDoc ssoId)
331    {
332       if (false == isTreeCacheAvailable(false))
333       {
334          logMissingCacheError();
335          return;
336       }
337       
338       // Check whether we are already handling this removal
339
//synchronized (beingLocallyRemoved)
340
{
341          if (beingLocallyRemoved.contains(ssoId))
342          {
343             return;
344          }
345          // Add this SSO to our list of in-process local removals so
346
// this.nodeRemoved() will ignore the removal
347
beingLocallyRemoved.add(ssoId);
348       }
349
350       if (log.isTraceEnabled())
351       {
352          log.trace("Registering logout of SSO " + ssoId +
353             " in clustered cache");
354       }
355
356       Fqn fqn = getSingleSignOnFqn(ssoId);
357       
358       //UserTransaction tx = null;
359
try
360       {
361          //tx = getNewTransaction();
362
//tx.begin();
363
removeFromTreeCache(fqn);
364          //tx.commit();
365
}
366       catch (Exception JavaDoc e)
367       {
368          /*
369          if (tx != null)
370          {
371             try
372             {
373                tx.rollback();
374             }
375             catch (Exception x) {}
376          }
377          */

378          log.error("Exception attempting to remove node " +
379             fqn.toString() + " from TreeCache", e);
380       }
381       finally
382       {
383          //synchronized (beingLocallyRemoved)
384
{
385             beingLocallyRemoved.remove(ssoId);
386          }
387       }
388    }
389
390
391    /**
392     * Queries the cluster for the existence of an SSO session with the given
393     * id, returning a <code>SingleSignOnEntry</code> if one is found.
394     *
395     * @param ssoId the id of the SSO session
396     * @return a <code>SingleSignOnEntry</code> created using information
397     * found on another cluster node, or <code>null</code> if no
398     * entry could be found.
399     */

400    public SingleSignOnEntry lookup(String JavaDoc ssoId)
401    {
402       if (false == isTreeCacheAvailable(false))
403       {
404          logMissingCacheError();
405          return null;
406       }
407
408       SingleSignOnEntry entry = null;
409       // Find the latest credential info from the cluster
410
Fqn fqn = getCredentialsFqn(ssoId);
411       //UserTransaction tx = null;
412
try
413       {
414          //tx = getNewTransaction();
415
//tx.begin();
416
SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn);
417          if (data != null)
418          {
419             entry = new SingleSignOnEntry(null,
420                data.getAuthType(),
421                data.getUsername(),
422                data.getPassword());
423          }
424          //tx.commit();
425
}
426       catch (Exception JavaDoc e)
427       {
428          /*
429          if (tx != null)
430          {
431             try
432             {
433                tx.rollback();
434             }
435             catch (Exception x) {}
436          }
437          */

438          log.error("caught exception looking up SSOCredentials for SSO id " +
439             ssoId, e);
440       }
441       return entry;
442    }
443
444
445    /**
446     * Notifies the cluster of the creation of a new SSO entry.
447     *
448     * @param ssoId the id of the SSO session
449     * @param authType the type of authenticator (BASIC, CLIENT-CERT, DIGEST
450     * or FORM) used to authenticate the SSO.
451     * @param username the username (if any) used for the authentication
452     * @param password the password (if any) used for the authentication
453     */

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

478    public void removeSession(String JavaDoc ssoId, Session session)
479    {
480       if (false == isTreeCacheAvailable(false))
481       {
482          logMissingCacheError();
483          return;
484       }
485       
486       // Check that this session removal is not due to our own deregistration
487
// of an SSO following receipt of a nodeRemoved() call
488
//synchronized(beingRemotelyRemoved)
489
{
490          if (beingRemotelyRemoved.contains(ssoId))
491          {
492             return;
493          }
494       }
495
496       if (log.isTraceEnabled())
497       {
498          log.trace("removeSession(): removing Session " + session.getId() +
499             " from cached session set for SSO " + ssoId);
500       }
501
502       Fqn fqn = getSessionsFqn(ssoId);
503       UserTransaction JavaDoc tx = null;
504       boolean removing = false;
505       try
506       {
507          tx = getNewTransaction();
508
509          tx.begin();
510          Set JavaDoc sessions = getSessionSet(fqn, false);
511          if (sessions != null)
512          {
513             sessions.remove(session.getId());
514             if (sessions.size() == 0)
515             {
516                // Add this SSO to our list of in-process local removals so
517
// this.nodeRemoved() will ignore the removal
518
//synchronized (beingLocallyRemoved)
519
{
520                   beingLocallyRemoved.add(ssoId);
521                }
522                removing = true;
523                // No sessions left; remove node
524
removeFromTreeCache(getSingleSignOnFqn(ssoId));
525             }
526             else
527             {
528                putInTreeCache(fqn, sessions);
529             }
530          }
531          tx.commit();
532       }
533       catch (Exception JavaDoc e)
534       {
535          if (tx != null)
536          {
537             try
538             {
539                tx.rollback();
540             }
541             catch (Exception JavaDoc x)
542             {
543             }
544          }
545          String JavaDoc sessId = (session == null ? "NULL" : session.getId());
546          log.error("caught exception removing session " + sessId +
547             " from SSO id " + ssoId, e);
548       }
549       finally
550       {
551          if (removing)
552          {
553             //synchronized (beingLocallyRemoved)
554
{
555                beingLocallyRemoved.remove(ssoId);
556             }
557          }
558       }
559    }
560
561
562    /**
563     * Notifies the cluster of an update of the security credentials
564     * associated with an SSO session.
565     *
566     * @param ssoId the id of the SSO session
567     * @param authType the type of authenticator (BASIC, CLIENT-CERT, DIGEST
568     * or FORM) used to authenticate the SSO.
569     * @param username the username (if any) used for the authentication
570     * @param password the password (if any) used for the authentication
571     */

572    public void updateCredentials(String JavaDoc ssoId, String JavaDoc authType,
573       String JavaDoc username, String JavaDoc password)
574    {
575       if (false == isTreeCacheAvailable(false))
576       {
577          logMissingCacheError();
578          return;
579       }
580
581       if (log.isTraceEnabled())
582       {
583          log.trace("Updating credentials for SSO " + ssoId +
584             " in clustered cache");
585       }
586
587       storeSSOData(ssoId, authType, username, password);
588    }
589
590    
591    // ------------------------------------------------------ TreeCacheListener
592

593    /**
594     * Does nothing
595     */

596    public void nodeCreated(Fqn fqn)
597    {
598       ; // do nothing
599
}
600
601    /**
602     * Does nothing
603     */

604    public void nodeLoaded(Fqn fqn)
605    {
606       ; // do nothing
607
}
608
609
610    /**
611     * Does nothing
612     */

613    public void nodeVisited(Fqn fqn)
614    {
615       ; // do nothing
616
}
617
618
619    /**
620     * Does nothing
621     */

622    public void cacheStarted(TreeCache cache)
623    {
624       ; // do nothing
625
}
626
627
628    /**
629     * Does nothing
630     */

631    public void cacheStopped(TreeCache cache)
632    {
633       ; // do nothing
634
}
635
636
637    /**
638     * Extracts an SSO session id from the Fqn and uses it in an invocation of
639     * {@link ClusteredSingleSignOn#deregister(String) ClusteredSingleSignOn.deregister(String)}.
640     * <p/>
641     * Ignores invocations resulting from TreeCache changes originated by
642     * this object.
643     *
644     * @param fqn the fully-qualified name of the node that was removed
645     */

646    public void nodeRemoved(Fqn fqn)
647    {
648       String JavaDoc ssoId = getIdFromFqn(fqn);
649
650       // Ignore messages generated by our own activity
651
//synchronized(beingLocallyRemoved)
652
{
653          if (beingLocallyRemoved.contains(ssoId))
654          {
655             return;
656          }
657       }
658       
659       //synchronized (beingRemotelyRemoved)
660
{
661          beingRemotelyRemoved.add(ssoId);
662       }
663
664       try
665       {
666          if (log.isTraceEnabled())
667          {
668             log.trace("received a node removed message for SSO " + ssoId);
669          }
670
671          ssoValve.deregister(ssoId);
672       }
673       finally
674       {
675          //synchronized(beingRemotelyRemoved)
676
{
677             beingRemotelyRemoved.remove(ssoId);
678          }
679       }
680
681    }
682
683
684    /**
685     * Extracts an SSO session id from the Fqn and uses it in an invocation of
686     * {@link ClusteredSingleSignOn#update ClusteredSingleSignOn.update()}.
687     * <p/>
688     * Only responds to modifications of nodes whose FQN's final segment is
689     * "credentials".
690     * <p/>
691     * Ignores invocations resulting from TreeCache changes originated by
692     * this object.
693     * <p/>
694     * Ignores invocations for SSO session id's that are not registered
695     * with the local SingleSignOn valve.
696     *
697     * @param fqn the fully-qualified name of the node that was modified
698     */

699    public void nodeModified(Fqn fqn)
700    {
701       // We are only interested in changes to the CREDENTIALS node
702
if (CREDENTIALS.equals(getTypeFromFqn(fqn)) == false)
703       {
704          return;
705       }
706
707       String JavaDoc ssoId = getIdFromFqn(fqn);
708
709       // Ignore invocations that come as a result of our additions
710
//synchronized(beingLocallyAdded)
711
{
712          if (beingLocallyAdded.contains(ssoId))
713          {
714             return;
715          }
716       }
717
718       SingleSignOnEntry sso = ssoValve.localLookup(ssoId);
719       if (sso == null || sso.getCanReauthenticate())
720       {
721          // No reason to update
722
return;
723       }
724
725       if (log.isTraceEnabled())
726       {
727          log.trace("received a credentials modified message for SSO " + ssoId);
728       }
729
730       // Put this SSO in the queue of those to be updated
731
credentialUpdater.enqueue(sso, ssoId);
732    }
733
734
735    /**
736     * Does nothing
737     */

738    public void viewChange(View new_view)
739    {
740       ; // do nothing
741
}
742
743
744    /**
745     * Does nothing. Called when a node is evicted (not the same as remove()).
746     *
747     * @param fqn
748     */

749    public void nodeEvicted(Fqn fqn)
750    {
751       // TODO do we need to handle this?
752
; // do nothing
753
}
754
755    
756    // ------------------------------------------------------------- Lifecycle
757

758
759    /**
760     * Add a lifecycle event listener to this component.
761     *
762     * @param listener The listener to add
763     */

764    public void addLifecycleListener(LifecycleListener listener)
765    {
766       lifecycle.addLifecycleListener(listener);
767    }
768
769
770    /**
771     * Get the lifecycle listeners associated with this lifecycle. If this
772     * Lifecycle has no listeners registered, a zero-length array is returned.
773     */

774    public LifecycleListener[] findLifecycleListeners()
775    {
776       return lifecycle.findLifecycleListeners();
777    }
778
779
780    /**
781     * Remove a lifecycle event listener from this component.
782     *
783     * @param listener The listener to remove
784     */

785    public void removeLifecycleListener(LifecycleListener listener)
786    {
787       lifecycle.removeLifecycleListener(listener);
788    }
789
790    /**
791     * Prepare for the beginning of active use of the public methods of this
792     * component. This method should be called before any of the public
793     * methods of this component are utilized. It should also send a
794     * LifecycleEvent of type START_EVENT to any registered listeners.
795     *
796     * @throws LifecycleException if this component detects a fatal error
797     * that prevents this component from being used
798     */

799    public void start() throws LifecycleException
800    {
801       // Validate and update our current component state
802
if (started)
803       {
804          throw new LifecycleException
805             ("TreeCacheSSOClusterManager already Started");
806       }
807       
808       // Start the thread we use to clear nodeModified events
809
credentialUpdater = new CredentialUpdater();
810
811       started = true;
812
813       // Notify our interested LifecycleListeners
814
lifecycle.fireLifecycleEvent(START_EVENT, null);
815    }
816
817
818    /**
819     * Gracefully terminate the active use of the public methods of this
820     * component. This method should be the last one called on a given
821     * instance of this component. It should also send a LifecycleEvent
822     * of type STOP_EVENT to any registered listeners.
823     *
824     * @throws LifecycleException if this component detects a fatal error
825     * that needs to be reported
826     */

827    public void stop() throws LifecycleException
828    {
829       // Validate and update our current component state
830
if (!started)
831       {
832          throw new LifecycleException
833             ("TreeCacheSSOClusterManager not Started");
834       }
835
836       credentialUpdater.stop();
837
838       started = false;
839
840       // Notify our interested LifecycleListeners
841
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
842    }
843
844    
845    // ------------------------------------------------------- Private Methods
846

847    private Object JavaDoc getFromTreeCache(Fqn fqn) throws Exception JavaDoc
848    {
849       Object JavaDoc[] args = new Object JavaDoc[]{fqn, KEY};
850       return server.invoke(getCacheObjectName(), "get", args, GET_SIGNATURE);
851    }
852
853    private Fqn getCredentialsFqn(String JavaDoc ssoid)
854    {
855       Object JavaDoc[] objs = new Object JavaDoc[]{SSO, ssoid, CREDENTIALS};
856       return new Fqn(objs);
857    }
858
859    private Fqn getSessionsFqn(String JavaDoc ssoid)
860    {
861       Object JavaDoc[] objs = new Object JavaDoc[]{SSO, ssoid, SESSIONS};
862       return new Fqn(objs);
863    }
864
865    private Fqn getSingleSignOnFqn(String JavaDoc ssoid)
866    {
867       Object JavaDoc[] objs = new Object JavaDoc[]{SSO, ssoid};
868       return new Fqn(objs);
869    }
870
871    /**
872     * Extracts an SSO session id from a fully qualified name object.
873     *
874     * @param fqn the Fully Qualified Name used by TreeCache
875     * @return the second element in the Fqn -- the SSO session id
876     */

877    private String JavaDoc getIdFromFqn(Fqn fqn)
878    {
879       return (String JavaDoc) fqn.get(1);
880    }
881
882    private InitialContext JavaDoc getInitialContext() throws NamingException JavaDoc
883    {
884       if (initialContext == null)
885       {
886          initialContext = new InitialContext JavaDoc();
887       }
888       return initialContext;
889    }
890
891    private Set JavaDoc getSessionSet(Fqn fqn, boolean create)
892       throws Exception JavaDoc
893    {
894       Set JavaDoc sessions = (Set JavaDoc) getFromTreeCache(fqn);
895       if (create && sessions == null)
896       {
897          sessions = new HashSet JavaDoc();
898       }
899       return sessions;
900    }
901
902    /**
903     * Extracts the SSO tree cache node type from a fully qualified name
904     * object.
905     *
906     * @param fqn the Fully Qualified Name used by TreeCache
907     * @return the last element in the Fqn -- either
908     * {@link #CREDENTIALS CREDENTIALS} or {@link #SESSIONS SESSIONS}.
909     */

910    private String JavaDoc getTypeFromFqn(Fqn fqn)
911    {
912       return (String JavaDoc) fqn.get(fqn.size() - 1);
913    }
914
915    private UserTransaction JavaDoc getNewTransaction() throws NamingException JavaDoc
916    {
917       try
918       {
919          UserTransaction JavaDoc t =
920             (UserTransaction JavaDoc) getInitialContext().lookup("UserTransaction");
921          return t;
922       }
923       catch (NamingException JavaDoc n)
924       {
925          // Discard the cached initial context
926
// in case there is a problem with it
927
initialContext = null;
928          throw n;
929       }
930    }
931
932    /**
933     * Checks whether an MBean is registered under the value of property
934     * "cacheObjectName".
935     *
936     * @param forceCheck check for availability whether or not it has already
937     * been positively established
938     * @return <code>true</code> if property <code>cacheName</code> has been
939     * set and points to a registered MBean.
940     */

941    private synchronized boolean isTreeCacheAvailable(boolean forceCheck)
942    {
943       if (forceCheck || treeCacheAvailable == false)
944       {
945          boolean available = (cacheObjectName != null);
946          if (available)
947          {
948             Set JavaDoc s = server.queryMBeans(cacheObjectName, null);
949             available = s.size() > 0;
950             if (available)
951             {
952                try
953                {
954                   registerAsTreeCacheListener(cacheObjectName);
955                   setMissingCacheErrorLogged(false);
956                }
957                catch (Exception JavaDoc e)
958                {
959                   log.error("Caught exception registering as listener to " +
960                      cacheObjectName, e);
961                   available = false;
962                }
963             }
964          }
965          treeCacheAvailable = available;
966       }
967       return treeCacheAvailable;
968    }
969
970    private void putInTreeCache(Fqn fqn, Object JavaDoc data) throws Exception JavaDoc
971    {
972       Object JavaDoc[] args = new Object JavaDoc[]{fqn, KEY, data};
973       server.invoke(getCacheObjectName(), "put", args, PUT_SIGNATURE);
974    }
975
976    /**
977     * Invokes an operation on the JMX server to register ourself as a
978     * listener on the TreeCache service.
979     *
980     * @throws Exception
981     */

982    private void registerAsTreeCacheListener(ObjectName JavaDoc listenTo)
983       throws Exception JavaDoc
984    {
985       server.invoke(listenTo, "addTreeCacheListener",
986          new Object JavaDoc[]{this},
987          new String JavaDoc[]{TreeCacheListener.class.getName()});
988       registeredAsListener = true;
989    }
990
991
992    /**
993     * Invokes an operation on the JMX server to register ourself as a
994     * listener on the TreeCache service.
995     *
996     * @throws Exception
997     */

998    private void removeAsTreeCacheListener(ObjectName JavaDoc removeFrom)
999       throws Exception JavaDoc
1000   {
1001      if (registeredAsListener && removeFrom != null)
1002      {
1003         server.invoke(removeFrom, "removeTreeCacheListener",
1004            new Object JavaDoc[]{this},
1005            new String JavaDoc[]{TreeCacheListener.class.getName()});
1006      }
1007   }
1008
1009   private void removeFromTreeCache(Fqn fqn) throws Exception JavaDoc
1010   {
1011      server.invoke(getCacheObjectName(), "remove",
1012         new Object JavaDoc[]{fqn},
1013         REMOVE_SIGNATURE);
1014   }
1015
1016   /**
1017    * Stores the given data to the clustered cache in a tree branch whose FQN
1018    * is the given SSO id. Stores the given credential data in a child node
1019    * named "credentials". If parameter <code>storeSessions</code> is
1020    * <code>true</code>, also stores an empty HashSet in a sibling node
1021    * named "sessions". This HashSet will later be used to hold session ids
1022    * associated with the SSO.
1023    * <p/>
1024    * Any items stored are stored under the key "key".
1025    *
1026    * @param ssoId the id of the SSO session
1027    * @param authType the type of authenticator (BASIC, CLIENT-CERT, DIGEST
1028    * or FORM) used to authenticate the SSO.
1029    * @param username the username (if any) used for the authentication
1030    * @param password the password (if any) used for the authentication
1031    */

1032   private void storeSSOData(String JavaDoc ssoId, String JavaDoc authType, String JavaDoc username,
1033      String JavaDoc password)
1034   {
1035      SSOCredentials data = new SSOCredentials(authType, username, password);
1036      // Add this SSO to our list of in-process local adds so
1037
// this.nodeModified() will ignore the addition
1038
//synchronized (beingLocallyAdded)
1039
{
1040         beingLocallyAdded.add(ssoId);
1041      }
1042      //UserTransaction tx = null;
1043
try
1044      {
1045         //tx = getNewTransaction();
1046
//tx.begin();
1047
putInTreeCache(getCredentialsFqn(ssoId), data);
1048         //tx.commit();
1049
}
1050      catch (Exception JavaDoc e)
1051      {
1052         /*
1053         if (tx != null)
1054         {
1055            try
1056            {
1057               tx.rollback();
1058            }
1059            catch (Exception x) {}
1060         }
1061         */

1062         log.error("Exception attempting to add TreeCache nodes for SSO " +
1063            ssoId, e);
1064      }
1065      finally
1066      {
1067         //synchronized (beingLocallyAdded)
1068
{
1069            beingLocallyAdded.remove(ssoId);
1070         }
1071      }
1072   }
1073
1074   private boolean isMissingCacheErrorLogged()
1075   {
1076      return missingCacheErrorLogged;
1077   }
1078
1079   private void setMissingCacheErrorLogged(boolean missingCacheErrorLogged)
1080   {
1081      this.missingCacheErrorLogged = missingCacheErrorLogged;
1082   }
1083
1084   private void logMissingCacheError()
1085   {
1086      StringBuffer JavaDoc msg = new StringBuffer JavaDoc("Cannot find TreeCache using ");
1087      msg.append(getCacheName());
1088      msg.append(" -- TreeCache must be started before ClusteredSingleSignOn ");
1089      msg.append("can handle requests");
1090
1091      if (isMissingCacheErrorLogged())
1092      {
1093         // Just log it as a warning
1094
log.warn(msg);
1095      }
1096      else
1097      {
1098         log.error(msg);
1099         // Set a flag so we don't relog this error over and over
1100
setMissingCacheErrorLogged(true);
1101      }
1102   }
1103
1104   // --------------------------------------------------------- Inner Classes
1105

1106   /**
1107    * Spawns a thread to handle updates of credentials
1108    */

1109   private class CredentialUpdater
1110      implements Runnable JavaDoc
1111   {
1112      private HashSet JavaDoc awaitingUpdate = new HashSet JavaDoc();
1113      private Thread JavaDoc updateThread;
1114      private boolean updateThreadSleeping = false;
1115      private boolean queueEmpty = true;
1116      private boolean stopped = false;
1117
1118      private CredentialUpdater()
1119      {
1120         updateThread =
1121            new Thread JavaDoc(this, "SSOClusterManager.CredentialUpdater");
1122         updateThread.setDaemon(true);
1123         updateThread.start();
1124      }
1125
1126      // ------------------------------------------------------ Runnable
1127

1128      public void run()
1129      {
1130         while (!stopped)
1131         {
1132            // Ensure that no runtime exceptions kill this thread
1133
try
1134            {
1135               updateThreadSleeping = false;
1136               // Get the current list of ids awaiting processing
1137
SSOWrapper[] ssos = null;
1138               synchronized (awaitingUpdate)
1139               {
1140                  ssos = new SSOWrapper[awaitingUpdate.size()];
1141                  ssos = (SSOWrapper[]) awaitingUpdate.toArray(ssos);
1142                  awaitingUpdate.clear();
1143                  queueEmpty = true;
1144               }
1145
1146               // Handle the credential update
1147
for (int i = 0; i < ssos.length; i++)
1148               {
1149                  processUpdate(ssos[i]);
1150               }
1151
1152               // Wait for another invocation of enqueue(). But,
1153
// first have to check in case it was invoked while we
1154
// were processing the previous bunch
1155
if (queueEmpty)
1156               {
1157                  try
1158                  {
1159                     // There is a slight possibility here of a race condition
1160
// between the above check for queueEmpty and another
1161
// thread accessing enqueue()'s check of
1162
// updateThreadSleeping. If this happens, the update
1163
// will not be processed by the local node until the
1164
// updateThread wakes up (30 secs) or is interrupted by
1165
// another update. This situation is quite unlikely,
1166
// as updates only happen 1) in odd configurations where
1167
// CLIENT-CERT authentication is used for some apps and
1168
// FORM or BASIC are used for others and 2) the user has
1169
// first logged in to a CLIENT-CERT app and later logs in
1170
// to a FORM/BASIC app. If such a race condition were to
1171
// occur, the only downside would be that if the user
1172
// accessed a FORM/BASIC app on this node before the local
1173
// update is processed, they would have to log in again.
1174
updateThreadSleeping = true;
1175                     updateThread.sleep(30000);
1176                  }
1177                  catch (InterruptedException JavaDoc e)
1178                  {
1179                     if (log.isTraceEnabled())
1180                     {
1181                        log.trace("CredentialUpdater: interrupted");
1182                     }
1183                     // process the next bunch
1184
}
1185               }
1186               else if (log.isTraceEnabled())
1187               {
1188                  log.trace("CredentialUpdater: more updates added while " +
1189                     "handling existing updates");
1190               }
1191            }
1192            catch (Exception JavaDoc e)
1193            {
1194               log.error("CredentialUpdater thread caught an exception", e);
1195            }
1196         }
1197      }
1198
1199      // ------------------------------------------------- Private Methods
1200

1201      /**
1202       * Adds an SSO id to the set of those awaiting credential updating, and
1203       * interrupts the update handler thread to notify it of the addition.
1204       *
1205       * @param sso the id of the SSO session whose local credentials
1206       * are to be updated
1207       */

1208      private void enqueue(SingleSignOnEntry sso, String JavaDoc ssoId)
1209      {
1210         synchronized (awaitingUpdate)
1211         {
1212            awaitingUpdate.add(new SSOWrapper(sso, ssoId));
1213            queueEmpty = false;
1214         }
1215         // Interrupt the update thread so it wakes up to process
1216
// the enqueued update. Only do this if its "sleeping" flag
1217
// is set so we don't inadvertently interrupt it while its
1218
// blocked waiting for a TreeCache lock to clear
1219
if (updateThreadSleeping)
1220         {
1221            updateThread.interrupt();
1222         }
1223      }
1224
1225      private void processUpdate(SSOWrapper wrapper)
1226      {
1227         if (wrapper.sso.getCanReauthenticate())
1228         {
1229            // No need to update
1230
return;
1231         }
1232
1233         Fqn fqn = getCredentialsFqn(wrapper.id);
1234         //UserTransaction tx = null;
1235
try
1236         {
1237            //tx = getNewTransaction();
1238
//tx.begin();
1239
SSOCredentials data = (SSOCredentials) getFromTreeCache(fqn);
1240            if (data != null)
1241            {
1242               // We want to release our read lock quickly, so get the needed
1243
// data from the cache, commit the tx, and then use the data
1244
String JavaDoc authType = data.getAuthType();
1245               String JavaDoc username = data.getUsername();
1246               String JavaDoc password = data.getPassword();
1247               //tx.commit();
1248

1249               if (log.isTraceEnabled())
1250               {
1251                  log.trace("CredentialUpdater: Updating credentials for SSO " +
1252                     wrapper.sso);
1253               }
1254
1255               synchronized (wrapper.sso)
1256               {
1257                  // Use the existing principal
1258
Principal JavaDoc p = wrapper.sso.getPrincipal();
1259                  wrapper.sso.updateCredentials(p, authType, username, password);
1260               }
1261            }
1262            /*
1263            else
1264            {
1265               tx.commit();
1266            }
1267            */

1268
1269         }
1270         catch (Exception JavaDoc e)
1271         {
1272            /*
1273            if (tx != null)
1274            {
1275               try
1276               {
1277                  tx.rollback();
1278               }
1279               catch (Exception x) {}
1280            }
1281            */

1282            log.error("Exception attempting to get SSOCredentials from " +
1283               "TreeCache node " + fqn.toString(), e);
1284         }
1285      }
1286
1287      /**
1288       * Stops the update handler thread.
1289       */

1290      private void stop()
1291      {
1292         stopped = true;
1293      }
1294
1295   } // end CredentialUpdater
1296

1297
1298   /**
1299    * Wrapper class that holds a SingleSignOnEntry and its id
1300    */

1301   private class SSOWrapper
1302   {
1303      private SingleSignOnEntry sso = null;
1304      private String JavaDoc id = null;
1305
1306      private SSOWrapper(SingleSignOnEntry entry, String JavaDoc ssoId)
1307      {
1308         this.sso = entry;
1309         this.id = ssoId;
1310      }
1311   }
1312
1313   // --------------------------------------------------------- Outer Classes
1314

1315   /**
1316    * Private class used to store authentication credentials in the TreeCache.
1317    * <p/>
1318    * For security, password accessor is private.
1319    */

1320   public static class SSOCredentials
1321      implements Serializable JavaDoc
1322   {
1323      static final long serialVersionUID = 5704877226920571663L;
1324      private String JavaDoc authType = null;
1325      private String JavaDoc password = null;
1326      private String JavaDoc username = null;
1327
1328      /**
1329       * Creates a new SSOCredentials.
1330       *
1331       * @param authType The authorization method used to authorize the
1332       * SSO (BASIC, CLIENT-CERT, DIGEST, FORM or NONE).
1333       * @param username The username of the user associated with the SSO
1334       * @param password The password of the user associated with the SSO
1335       */

1336      private SSOCredentials(String JavaDoc authType, String JavaDoc username, String JavaDoc password)
1337      {
1338         this.authType = authType;
1339         this.username = username;
1340         this.password = password;
1341      }
1342
1343      /**
1344       * Gets the username of the user associated with the SSO.
1345       *
1346       * @return the username
1347       */

1348      public String JavaDoc getUsername()
1349      {
1350         return username;
1351      }
1352
1353      /**
1354       * Gets the authorization method used to authorize the SSO.
1355       *
1356       * @return "BASIC", "CLIENT-CERT", "DIGEST" or "FORM"
1357       */

1358      public String JavaDoc getAuthType()
1359      {
1360         return authType;
1361      }
1362
1363      /**
1364       * Gets the password of the user associated with the SSO.
1365       *
1366       * @return the password, or <code>null</code> if the authorization
1367       * type was DIGEST or CLIENT-CERT.
1368       */

1369      private String JavaDoc getPassword()
1370      {
1371         return password;
1372      }
1373
1374   } // end SSOCredentials
1375

1376} // end TreeCacheSSOClusterManager
1377

1378
Popular Tags