View Javadoc
1   /**
2    * Copyright 2014 Internet2
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  /*
17    Copyright (C) 2004-2007 University Corporation for Advanced Internet Development, Inc.
18    Copyright (C) 2004-2007 The University Of Chicago
19  
20    Licensed under the Apache License, Version 2.0 (the "License");
21    you may not use this file except in compliance with the License.
22    You may obtain a copy of the License at
23  
24      http://www.apache.org/licenses/LICENSE-2.0
25  
26    Unless required by applicable law or agreed to in writing, software
27    distributed under the License is distributed on an "AS IS" BASIS,
28    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29    See the License for the specific language governing permissions and
30    limitations under the License.
31  */
32  
33  package edu.internet2.middleware.grouper;
34  import java.io.Serializable;
35  import java.util.ArrayList;
36  import java.util.Date;
37  import java.util.LinkedHashMap;
38  import java.util.List;
39  import java.util.Map;
40  
41  import org.apache.commons.lang.StringUtils;
42  import org.apache.commons.lang.builder.ToStringBuilder;
43  import org.apache.commons.lang.builder.ToStringStyle;
44  import org.apache.commons.lang.time.StopWatch;
45  import org.apache.commons.logging.Log;
46  
47  import edu.internet2.middleware.grouper.annotations.GrouperIgnoreClone;
48  import edu.internet2.middleware.grouper.annotations.GrouperIgnoreDbVersion;
49  import edu.internet2.middleware.grouper.annotations.GrouperIgnoreFieldConstant;
50  import edu.internet2.middleware.grouper.exception.GrouperException;
51  import edu.internet2.middleware.grouper.exception.GrouperSessionException;
52  import edu.internet2.middleware.grouper.exception.MemberNotFoundException;
53  import edu.internet2.middleware.grouper.exception.SessionException;
54  import edu.internet2.middleware.grouper.internal.util.GrouperUuid;
55  import edu.internet2.middleware.grouper.internal.util.Quote;
56  import edu.internet2.middleware.grouper.misc.E;
57  import edu.internet2.middleware.grouper.misc.GrouperCheckConfig;
58  import edu.internet2.middleware.grouper.misc.GrouperDAOFactory;
59  import edu.internet2.middleware.grouper.misc.GrouperSessionHandler;
60  import edu.internet2.middleware.grouper.misc.GrouperStartup;
61  import edu.internet2.middleware.grouper.misc.M;
62  import edu.internet2.middleware.grouper.privs.AccessAdapter;
63  import edu.internet2.middleware.grouper.privs.AccessResolver;
64  import edu.internet2.middleware.grouper.privs.AccessResolverFactory;
65  import edu.internet2.middleware.grouper.privs.AttributeDefResolver;
66  import edu.internet2.middleware.grouper.privs.AttributeDefResolverFactory;
67  import edu.internet2.middleware.grouper.privs.GrouperAttributeDefAdapter;
68  import edu.internet2.middleware.grouper.privs.NamingAdapter;
69  import edu.internet2.middleware.grouper.privs.NamingResolver;
70  import edu.internet2.middleware.grouper.privs.NamingResolverFactory;
71  import edu.internet2.middleware.grouper.privs.PrivilegeHelper;
72  import edu.internet2.middleware.grouper.session.GrouperSessionResult;
73  import edu.internet2.middleware.grouper.subj.InternalSourceAdapter;
74  import edu.internet2.middleware.grouper.subj.SubjectHelper;
75  import edu.internet2.middleware.grouper.util.GrouperUtil;
76  import edu.internet2.middleware.grouper.validator.GrouperValidator;
77  import edu.internet2.middleware.grouper.validator.NotNullValidator;
78  import edu.internet2.middleware.subject.Subject;
79  
80  
81  /** 
82   * Context for interacting with the Grouper API and Groups Registry.
83   * <p/>
84   * @author  blair christensen.
85   * @version $Id: GrouperSession.java,v 1.101 2009-11-05 20:06:42 isgwb Exp $
86   */
87  @SuppressWarnings("serial")
88  public class GrouperSession implements Serializable {
89  
90    /**
91     * @see java.lang.Object#finalize()
92     */
93    @Override
94    protected void finalize() throws Throwable {
95      super.finalize();
96      stopQuietly(this);
97    }
98  
99    /**
100    * if we should take into consideration that we are a wheel member (or act as self if false)
101    */
102   private boolean considerIfWheelMember = true;
103 
104   /**
105    * if we should take into consideration that we are a wheel member (or act as self if false)
106    * @return if considering if wheel member
107    */
108   public boolean isConsiderIfWheelMember() {
109     return this.considerIfWheelMember;
110   }
111 
112   /**
113    * if we should take into consideration that we are a wheel member (or act as self if false)
114    * @param considerIfWheelMember1
115    */
116   public void setConsiderIfWheelMember(boolean considerIfWheelMember1) {
117     this.considerIfWheelMember = considerIfWheelMember1;
118   }
119 
120   /**
121    * throw illegal state if stopped
122    */
123   private void internal_ThrowIllegalStateIfStopped() {
124     if (this.subject == null) {
125       throw new IllegalStateException("Grouper session subject is null, probably since it is stopped.  " +
126       		"Dont use it anymore, start another");
127     }
128   }
129   
130   /** logger */
131   private static final Log LOG = GrouperUtil.getLog(GrouperSession.class);
132 
133   /**
134    * store the grouper connection in thread local so other classes can get it.
135    * this is only for inverse of control.  This has priority over the 
136    * static session set from start()
137    */
138   private static ThreadLocal<List<GrouperSession>> staticSessions = new ThreadLocal<List<GrouperSession>>();
139 
140   /**
141    * holds a thread local of the current grouper session.
142    * this is set from a GrouperSesssion.start().  Note the 
143    * inverse of control sessions have priority
144    */
145   private static ThreadLocal<GrouperSession> staticGrouperSession = new ThreadLocal<GrouperSession>();
146   
147   /** */
148   @GrouperIgnoreDbVersion
149   @GrouperIgnoreFieldConstant
150   @GrouperIgnoreClone
151   private transient AccessResolver  accessResolver;
152 
153   /** */
154   @GrouperIgnoreDbVersion
155   @GrouperIgnoreFieldConstant
156   @GrouperIgnoreClone
157   private transient AttributeDefResolver  attributeDefResolver;
158 
159   /** */
160   @GrouperIgnoreDbVersion
161   @GrouperIgnoreFieldConstant
162   @GrouperIgnoreClone
163   private Member          cachedMember;
164 
165   /** */
166   @GrouperIgnoreDbVersion
167   @GrouperIgnoreFieldConstant
168   @GrouperIgnoreClone
169   private transient NamingResolver  namingResolver;
170 
171   /** */
172   @GrouperIgnoreDbVersion
173   @GrouperIgnoreFieldConstant
174   @GrouperIgnoreClone
175   private transient GrouperSession  rootSession;
176 
177   /** */
178   private String          memberUUID;
179 
180   /** */
181   private long            startTimeLong;
182 
183   /** */
184   @GrouperIgnoreDbVersion
185   private Subject         subject;
186 
187   /** */
188   private String          uuid;
189 
190 
191   /**
192    * Default constructor.  Dont call this, use the factory: start(Subject)
193    * <p/>
194    * @since   1.2.0
195    */
196   public GrouperSession() {
197     this.cachedMember = null;
198     this.rootSession  = null;
199   } 
200 
201   /**
202    * stop a session quietly
203    * @param session
204    */
205   public static void stopQuietly(GrouperSession session) {
206     if (session != null) {
207       try {
208         session.stop();
209       } catch (Exception e) {
210         LOG.error(e);
211       }
212     }
213 
214   }
215   
216   /**
217    * start a session based on a sourceId and subjectId
218    * @param sourceId if null search all sources
219    * @param subjectId
220    * @return return the GrouperSession
221    */
222   public static GrouperSession startBySubjectIdAndSource(final String subjectId, final String sourceId) {
223     
224     return startBySubjectIdAndSource(subjectId, sourceId, true);
225     
226   }
227   
228   /**
229    * start a session based on a sourceId and subjectId
230    * @param sourceId if null search all sources
231    * @param subjectId
232    * @param addToThreadLocal true if it should be in threadlocal, false if not
233    * @return return the GrouperSession
234    */
235   public static GrouperSession startBySubjectIdAndSource(final String subjectId, final String sourceId, 
236       boolean addToThreadLocal) {
237 
238     Subject subject = null;
239     
240     GrouperSession grouperSession = GrouperSession.staticGrouperSession(false);
241     boolean startedSession = false;
242     
243     try {
244       
245       if (grouperSession == null) {
246         grouperSession = GrouperSession.startRootSession(false);
247         startedSession = true;
248       }
249       
250       if (!PrivilegeHelper.isWheelOrRoot(grouperSession.getSubject())) {
251         grouperSession = grouperSession.internal_getRootSession();
252       }
253       
254       subject = (Subject)GrouperSession.callbackGrouperSession(grouperSession, new GrouperSessionHandler() {
255 
256         /**
257          * 
258          */
259         @Override
260         public Object callback(GrouperSession grouperSession)
261             throws GrouperSessionException {
262 
263           if (StringUtils.isBlank(sourceId)) {
264             return SubjectFinder.findById(subjectId, true);
265           }
266           return SubjectFinder.findByIdAndSource(subjectId, sourceId, true);
267           
268         }
269         
270       });
271       
272       
273       
274     } finally {
275       if (startedSession) {
276         GrouperSession.stopQuietly(grouperSession);
277       }
278     }
279     
280     return GrouperSession.start(subject, addToThreadLocal);
281     
282   }
283   
284   /**
285    * start a session based on a sourceId and subjectId
286    * @param sourceId if null search all sources
287    * @param subjectIdentifier
288    * @return return the GrouperSession
289    */
290   public static GrouperSession startBySubjectIdentifierAndSource(final String subjectIdentifier, final String sourceId) {
291     return startBySubjectIdentifierAndSource(subjectIdentifier, sourceId, true);
292   }
293   
294   /**
295    * start a session based on a sourceId and subjectId
296    * @param subjectIdentifier
297    * @param sourceId if null search all sources
298    * @param addToThreadLocal 
299    * @return return the GrouperSession
300    */
301   public static GrouperSession startBySubjectIdentifierAndSource(final String subjectIdentifier, final String sourceId, boolean addToThreadLocal) {
302     Subject subject = null;
303     
304     GrouperSession grouperSession = GrouperSession.staticGrouperSession(false);
305     boolean startedSession = false;
306     
307     try {
308       
309       if (grouperSession == null) {
310         grouperSession = GrouperSession.startRootSession(false);
311         startedSession = true;
312       }
313       
314       if (!PrivilegeHelper.isWheelOrRoot(grouperSession.getSubject())) {
315         grouperSession = grouperSession.internal_getRootSession();
316       }
317       
318       subject = (Subject)GrouperSession.callbackGrouperSession(grouperSession, new GrouperSessionHandler() {
319 
320         /**
321          * 
322          */
323         @Override
324         public Object callback(GrouperSession grouperSession)
325             throws GrouperSessionException {
326 
327           if (StringUtils.isBlank(sourceId)) {
328             return SubjectFinder.findByIdentifier(subjectIdentifier, true);
329           }
330           return SubjectFinder.findByIdentifierAndSource(subjectIdentifier, sourceId, true);
331           
332         }
333         
334       });
335       
336       
337       
338     } finally {
339       if (startedSession) {
340         GrouperSession.stopQuietly(grouperSession);
341       }
342     }
343     
344     return GrouperSession.start(subject, addToThreadLocal);
345     
346   }
347   
348   /**
349    * Start a session for interacting with the Grouper API.
350    * This adds the session to the threadlocal.    This has 
351    * threadlocal implications, so start and stop these hierarchically,
352    * do not alternate.  If you need to, use the callback inverse of control.
353    * <pre class="eg">
354    * // Start a Grouper API session.
355    * GrouperSession s = GrouperSession.subject);
356    * </pre>
357    * @param   subject   Start session as this {@link Subject}.
358    * @return  A Grouper API session.
359    * @throws  SessionException
360    */
361   public static GrouperSession start(Subject subject) 
362     throws SessionException {
363     
364     return start(subject, true);
365   }
366 
367   /**
368    * Start a session for interacting with the Grouper API.
369    * This adds the session to the threadlocal.    This has 
370    * threadlocal implications, so start and stop these hierarchically,
371    * do not alternate.  If you need to, use the callback inverse of control.
372    * 
373    * This will not start a session if it is already started.
374    * If it is started as a different user, it will start
375    * 
376    * <pre class="eg">
377    * // Start a Grouper API session.
378    * GrouperSession s = GrouperSession.subject);
379    * </pre>
380    * @param   subject   Start session as this {@link Subject}.
381    * @return  A Grouper API session.
382    * @throws  SessionException
383    */
384   public static GrouperSessionResult startIfNotStarted(Subject subject) 
385     throws SessionException {
386     
387     if (subject == null) {
388       throw new NullPointerException("subject is null");
389     }
390     
391     GrouperSessionResultesult.html#GrouperSessionResult">GrouperSessionResult grouperSessionResult = new GrouperSessionResult();
392     
393     GrouperSession grouperSession = staticGrouperSession(false);
394     
395     //if there is a session and it is started and same user, use that
396     if (grouperSession != null) {
397       if (SubjectHelper.eq(subject, grouperSession.getSubject())) {
398         grouperSessionResult.setCreated(false);
399         grouperSessionResult.setGrouperSession(grouperSession);
400         return grouperSessionResult;
401       }
402     }
403 
404     grouperSession = start(subject, true);
405     grouperSessionResult.setCreated(true);
406     grouperSessionResult.setGrouperSession(grouperSession);
407     return grouperSessionResult;
408     
409   }
410 
411   /**
412    * Start a session for interacting with the Grouper API.
413    * This adds the session to the threadlocal.    This has 
414    * threadlocal implications, so start and stop these hierarchically,
415    * do not alternate.  If you need to, use the callback inverse of control.
416    * This uses 
417    * <pre class="eg">
418    * // Start a Grouper API session.
419    * GrouperSession s = GrouperSession.start(subject);
420    * </pre>
421    * @param addToThreadLocal true to add this to the grouper session
422    * threadlocal which replaces the current one
423    * @return  A Grouper API session.
424    * @throws  SessionException
425    */
426   public static GrouperSession startRootSession(boolean addToThreadLocal) throws SessionException {
427     
428     return start(SubjectFinder.findRootSubject(), addToThreadLocal);
429   }
430 
431   /**
432    * Start a session for interacting with the Grouper API.
433    * This adds the session to the threadlocal.    This has 
434    * threadlocal implications, so start and stop these hierarchically,
435    * do not alternate.  If you need to, use the callback inverse of control.
436    * This uses 
437    * <pre class="eg">
438    * // Start a Grouper API session.
439    * GrouperSession s = GrouperSession.start(subject);
440    * </pre>
441    * @return  A Grouper API session.
442    * @throws  SessionException
443    */
444   public static GrouperSession startRootSession()
445     throws SessionException {
446     return startRootSession(true);
447   }
448 
449   /**
450    * grouper system member uuid
451    */
452   private static final String GROUPER_SYSTEM_MEMBER_UUID = "41b11bed121c4248bdaa8866b981a5b3";
453   
454   /**
455    * Start a session for interacting with the Grouper API.  This has 
456    * threadlocal implications, so start and stop these hierarchically,
457    * do not alternate.  If you need to, use the callback inverse of control.
458    * <pre class="eg">
459    * // Start a Grouper API session.
460    * GrouperSession s = GrouperSession.start(subject);
461    * </pre>
462    * @param   subject   Start session as this {@link Subject}.
463    * @param addToThreadLocal true to add this to the grouper session
464    * threadlocal which replaces the current one.  Though if in the context of a callback,
465    * the callback has precedence, and you should use an inner callback to preempt it (callbackGrouperSession)
466    * @return  A Grouper API session.
467    * @throws  SessionException
468    */
469   public static GrouperSession start(Subject subject, boolean addToThreadLocal) 
470     throws SessionException
471   {
472     if (subject == null) {
473       String idLog = "(subject is null)";
474       String msg = E.S_START + idLog;
475       LOG.fatal(msg);
476       throw new SessionException(msg);
477     }
478     
479     Map<String, Object> debugMap = LOG.isDebugEnabled() ? new LinkedHashMap<String, Object>() : null;
480 
481     if (LOG.isDebugEnabled()) {
482       debugMap.put("method", "start(subject,threadLocal)");
483       debugMap.put("subjectId", subject.getId());
484       debugMap.put("threadLocal", addToThreadLocal);
485     }
486     GrouperSession s = null;
487     try {
488       StopWatch sw = new StopWatch();
489       sw.start();
490       
491       s   =  new GrouperSession();
492       s.setSubject(subject);
493       s.getMember();
494       if (LOG.isDebugEnabled()) {
495         debugMap.put("hash", s.hashCode());
496       }
497         s.setStartTimeLong( new Date().getTime() );
498         s.setUuid( GrouperUuid.getUuid() );
499       
500       sw.stop();
501       if (LOG.isInfoEnabled()) {
502         LOG.info("[" + s.toString() + "] " + M.S_START + " (" + sw.getTime() + "ms)");
503       }
504 
505       if (addToThreadLocal) {
506         if (LOG.isDebugEnabled()) {
507           GrouperSession tempGrouperSession = staticGrouperSession.get();
508           if (tempGrouperSession == null || tempGrouperSession.getSubject() == null) {
509             debugMap.put("replacingSession", "null");
510           } else {
511             debugMap.put("replacingSession", tempGrouperSession.getSubject().getId() + ", " + tempGrouperSession.hashCode());
512           }          
513         }
514         //add to threadlocal
515         staticGrouperSession.set(s);
516       }
517     } finally {
518       if (LOG.isDebugEnabled()) {
519         logAddThreadLocal(debugMap, "");
520         LOG.debug("Stack: " + GrouperUtil.stack());
521         LOG.debug(GrouperUtil.mapToString(debugMap));
522       }
523     }
524     return s;
525   } 
526 
527   /**
528    * add thread local info to debug map
529    * @param debugMap
530    * @param prefix for log message if multiple in one map
531    */
532   private static void logAddThreadLocal(Map<String, Object> debugMap, String prefix) {
533     if (LOG.isDebugEnabled()) {
534       {
535         GrouperSession grouperSession = staticGrouperSession.get();
536         Subject subject = grouperSession == null ? null : grouperSession.getSubject();
537         if (grouperSession == null || subject == null) {
538           debugMap.put(prefix + "staticSession", "null");
539         } else {
540           debugMap.put(prefix + "staticSession", subject.getId());
541         }
542       }
543       
544       List<GrouperSession> staticGrouperSessions = staticSessions.get();
545       if (GrouperUtil.length(staticGrouperSessions) == 0) {
546         debugMap.put(prefix + "staticSessions", "0");
547       } else {
548         int i=0;
549         for (GrouperSession grouperSession : staticGrouperSessions) {
550           Subject subject = grouperSession == null ? null : grouperSession.getSubject();
551           if (grouperSession == null || subject == null) {
552             debugMap.put(prefix + "staticSessions_" + i, "null");
553           } else {
554             debugMap.put(prefix + "staticSessions_" + i, subject.getId());
555           }
556           i++;
557         }
558       }
559     }
560   }
561   
562   /**
563    * @param s 
564    * @throws  IllegalStateException
565    * @since   1.2.0
566    */
567   public static void validate(GrouperSession s) 
568     throws  IllegalStateException
569   {
570     NotNullValidator v = NotNullValidator.validate(s);
571     if (v.isInvalid()) {
572       throw new IllegalStateException(E.SV_O);
573     }
574     s.validate();
575   } // public static void validate(s)
576 
577 
578   // PUBLIC INSTANCE METHODS //
579 
580   /**
581    * Get name of class implenting {@link AccessAdapter} privilege interface.
582    * <pre class="eg">
583    * String klass = s.getAccessClass();
584    * </pre>
585    * @return access class
586    */
587   public String getAccessClass() {
588     return GrouperAccessAdapter.class.getName(); 
589   } 
590 
591   /**
592    * Get name of class implenting {@link AccessAdapter} privilege interface.
593    * <pre class="eg">
594    * String klass = s.getAccessClass();
595    * </pre>
596    * @return access class
597    */
598   public String getAttributeDefClass() {
599     return GrouperAttributeDefAdapter.class.getName();
600   } 
601 
602   /**
603    * @return  <code>AccessResolver</code> used by this session.
604    * @since   1.2.1
605    */
606   public AccessResolver getAccessResolver() {
607     this.internal_ThrowIllegalStateIfStopped();
608     if (this.accessResolver == null) {
609       this.accessResolver = AccessResolverFactory.getInstance(this);
610     }
611     return this.accessResolver;
612   }
613 
614    /**
615    * Get the {@link Member} associated with this API session.
616    * <pre class="eg">
617    * Member m = s.getMember(); 
618    * </pre>
619    * <p>
620    * As of 1.2.0, this method throws an {@link IllegalStateException} instead of
621    * a {@link NullPointerException} when the member cannot be retrieved.
622    * </p>
623    * @return  A {@link Member} object.
624    * @throws  IllegalStateException if {@link Member} cannot be returned.
625    */
626   public Member getMember() 
627     throws  IllegalStateException
628   {
629     this.internal_ThrowIllegalStateIfStopped();
630     if ( this.cachedMember != null ) {
631       return this.cachedMember;
632     }
633     
634     //  this will create the member if it doesn't already exist
635     if (GrouperStartup.isFinishedStartupSuccessfully()) {
636       if (InternalSourceAdapter.instance().rootSubject(subject)) {
637         // if root, then have a default uuid
638         this.cachedMember   = MemberFinder.internal_findBySubject(subject, GROUPER_SYSTEM_MEMBER_UUID, true);
639       } else {
640         
641         try {
642           this.cachedMember   = MemberFinder.internal_findBySubject(subject, null, true);
643         }
644         catch (MemberNotFoundException eShouldNeverHappen) {
645           throw new IllegalStateException( 
646             "this should never happen: " + eShouldNeverHappen.getMessage(), eShouldNeverHappen
647           );
648         }
649       }
650     } else {
651       //try to get it
652       try {
653         this.cachedMember   = MemberFinder.internal_findBySubject(subject, null, true);
654       } catch (Exception e) {
655         // ignore, grouper hasnt started yet, so ignore
656         LOG.debug("error finding subject: " + SubjectHelper.getPretty(this.subject), e);
657       }
658       if (this.cachedMember == null && InternalSourceAdapter.instance().rootSubject(subject)) {
659         // if we havent started yet, hard code this...
660         this.cachedMember   = new Member();
661         this.cachedMember.setSubjectId(subject.getId());
662         this.cachedMember.setSubjectSourceId(subject.getSourceId());
663         this.cachedMember.setUuid(GROUPER_SYSTEM_MEMBER_UUID);
664       }
665     }
666     
667     
668     return this.cachedMember;
669   } 
670 
671   /**
672    * Get name of class implenting {@link NamingAdapter} privilege interface.
673    * <pre class="eg">
674    * String klass = s.getNamingClass();
675    * </pre>
676    * @return naming class
677    */
678   public String getNamingClass() {
679     return GrouperNamingAdapter.class.getName(); 
680   } 
681 
682   /**
683    * @return  <code>NamingResolver</code> used by this session.
684    * @since   1.2.1
685    */
686   public NamingResolver getNamingResolver() {
687     if (this.namingResolver == null) {
688       this.namingResolver = NamingResolverFactory.getInstance(this);
689     }
690     return this.namingResolver;
691   }
692 
693   /**
694    * Get this session's id.
695    * <pre class="eg">
696    * String id = s.internal_getSessionId();
697    * </pre>
698    * @return  The session id.
699    */
700   public String getSessionId() {
701     return this.getUuid();
702   } // public String getSessionId()
703 
704   /**
705    * Get this session's start time.
706    * <pre class="eg">
707    * Date startTime = s.getStartTime();
708    * </pre>
709    * @return  This session's start time.
710    */
711   public Date getStartTime() {
712     this.internal_ThrowIllegalStateIfStopped();
713     return new Date( this.getStartTimeLong() );
714   } // public Date getStartTime()
715 
716   /**
717    * Get the {@link Subject} associated with this API session.
718    * <pre class="eg">
719    * Subject subj = s.getSubject(); 
720    * </pre>
721    * @return  A {@link Subject} object.
722    * @throws  GrouperException
723    */
724   public Subject getSubject() 
725     throws  GrouperException
726   {
727     this.internal_ThrowIllegalStateIfStopped();
728     return this.subject;
729   } // public Subject getSubject()
730 
731   /**
732    * Get the {@link Subject} associated with this API session.
733    * <pre class="eg">
734    * Subject subj = s.getSubject(); 
735    * </pre>
736    * @return  A {@link Subject} object.
737    * @throws  GrouperException
738    */
739   public Subject getSubjectDb() 
740     throws  GrouperException
741   {
742     return this.subject;
743   } // public Subject getSubject()
744 
745   /**
746    * Stop this API session.
747    * <pre class="eg">
748    * s.stop();
749    * </pre>
750    * @throws SessionException 
751    */
752   public void stop()  throws  SessionException
753   {
754     Map<String, Object> debugMap = LOG.isDebugEnabled() ? new LinkedHashMap<String, Object>() : null;
755     if (LOG.isDebugEnabled()) {
756       debugMap.put("method", "stop()");
757       debugMap.put("hash", this.hashCode());
758       if (this.subject == null) {
759         debugMap.put("subject", "null");
760       } else {
761         debugMap.put("subject", this.subject.getId());
762       }
763     }
764     try {
765       //remove from threadlocal if this is the one on threadlocal (might not be due
766       //to nesting)
767       if (this == staticGrouperSession.get()) {
768         staticGrouperSession.remove();
769       }
770       
771       if (this.accessResolver != null) {
772         this.accessResolver.stop();
773       }
774       if (this.attributeDefResolver != null) {
775         this.attributeDefResolver.stop();
776       }
777       if (this.namingResolver != null) {
778         this.namingResolver.stop();
779       }
780       
781       //stop the root
782       if (this.rootSession != null) {
783         this.rootSession.stop();
784       }
785       
786       
787       
788       //set some fields to null
789       this.subject = null;
790       this.accessResolver = null;
791       this.attributeDefResolver = null;
792       this.cachedMember = null;
793       this.memberUUID = null;
794       this.namingResolver = null;
795       this.rootSession = null;
796       this.uuid = null;
797     } finally {
798       if (LOG.isDebugEnabled()) {
799         logAddThreadLocal(debugMap, "");
800         LOG.debug("Stack: " + GrouperUtil.stack());
801         LOG.debug(debugMap);
802       }
803     }
804   } 
805 
806   /**
807    * 
808    * @see java.lang.Object#toString()
809    */
810   public String toString() {
811     return new ToStringBuilder(this, ToStringStyle.SIMPLE_STYLE)
812       .append( "session_id",   this.getUuid()                                        )
813       .append( "subject_id",   Quote.single( this.getSubject().getId() )             )
814       .append( "subject_type", Quote.single( this.getSubject().getType().getName() ) )
815       .toString();
816   } 
817 
818   /**
819    * @throws  IllegalStateException
820    * @since   1.2.0
821    */
822   public void validate() 
823     throws  IllegalStateException
824   {
825     GrouperValidator v = NotNullValidator.validate( this.getMemberUuid() );
826     if (v.isInvalid()) {
827       throw new IllegalStateException(E.SV_M);
828     }
829     v = NotNullValidator.validate( this.getUuid() );  
830     if (v.isInvalid()) {
831       throw new IllegalStateException(E.SV_I);
832     }
833   } 
834 
835   /**
836    * 
837    * @return the grouper session
838    * @throws GrouperException
839    */
840   public GrouperSession internal_getRootSession() 
841     throws  GrouperException
842   {
843     // TODO 20070417 deprecate if possible
844     if (this.rootSession == null) {
845       GrouperSession/GrouperSession.html#GrouperSession">GrouperSession rs = new GrouperSession();
846       rs.setMemberUuid( MemberFinder.internal_findRootMember().getUuid() );
847       rs.setStartTimeLong( new Date().getTime() );
848       rs.setSubject( SubjectFinder.findRootSubject() );
849       rs.setUuid( GrouperUuid.getUuid() );
850       this.rootSession = rs;
851     }
852     return this.rootSession;
853   } 
854 
855 
856   /**
857    * @return member uuid
858    * @since   1.2.0
859    */
860   public String getMemberUuid() {
861     if (StringUtils.isBlank(this.memberUUID)) {
862       this.memberUUID = this.getMember().getUuid();
863     }
864     return this.memberUUID;
865   }
866 
867   /**
868    * @return start time
869    * @since   1.2.0
870    */
871   public long getStartTimeLong() {
872     return this.startTimeLong;
873   }
874 
875   /**
876    * @return uuid
877    * @since   1.2.0
878    */
879   public String getUuid() {
880     return this.uuid;
881   }
882 
883   /**
884    * @param memberUUID1 
885    * @since   1.2.0
886    */
887   public void setMemberUuid(String memberUUID1) {
888     this.memberUUID = memberUUID1;
889   
890   }
891 
892   /**
893    * @param startTime1 
894    * @since   1.2.0
895    */
896   public void setStartTimeLong(long startTime1) {
897     this.startTimeLong = startTime1;
898   
899   }
900 
901   /**
902    * @param subject1 
903    * @since   1.2.0
904    */
905   public void setSubject(Subject subject1) {
906     this.subject = subject1;
907   
908   }
909 
910   /**
911    * @param uuid1 
912    * @since   1.2.0
913    */
914   public void setUuid(String uuid1) {
915     this.uuid = uuid1;
916   
917   }
918 
919   /**
920    * @return the string
921    * @since   1.2.0
922    */
923   public String toStringDto() {
924     return new ToStringBuilder(this)
925       .append( "memberUuid", this.getMemberUuid()  )
926       .append( "startTime",  this.getStartTime()   )
927       .append( "uuid",       this.getUuid() )
928       .toString();
929   }
930 
931   /**
932    * @return  <code>AttributeDefResolver</code> used by this session.
933    * @since   1.2.1
934    */
935   public AttributeDefResolver getAttributeDefResolver() {
936     this.internal_ThrowIllegalStateIfStopped();
937     if (this.attributeDefResolver == null) {
938       this.attributeDefResolver = AttributeDefResolverFactory.getInstance(this);
939     }
940     return this.attributeDefResolver;
941   }
942 
943   /**
944    * Start a root session for interacting with the Grouper API.
945    * This adds the session to the threadlocal.    This has 
946    * threadlocal implications, so start and stop these hierarchically,
947    * do not alternate.  If you need to, use the callback inverse of control.
948    * 
949    * This will not start a session if it is already started.
950    * If it is started as a different user, it will start
951    * 
952    * @return  A Grouper API session result.
953    * @throws  SessionException
954    */
955   public static GrouperSessionResult startRootSessionIfNotStarted() throws SessionException {
956     
957     return startIfNotStarted(SubjectFinder.findRootSubject());
958 
959   }
960 
961   /**
962    * call this to send a callback for the grouper session object. cant use
963    * inverse of control for this since it runs it.  Any method in the inverse of
964    * control can access the grouper session in a threadlocal
965    * 
966    * @param grouperSession is the session to do an inverse of control on
967    * 
968    * @param grouperSessionHandler
969    *          will get the callback
970    * @return the object returned from the callback
971    * @throws GrouperSessionException
972    *           if there is a problem, will preserve runtime exceptions so they are
973    *           thrown to the caller.  The GrouperSessionException wraps the underlying exception
974    */
975   public static Object callbackGrouperSession(GrouperSession grouperSession, GrouperSessionHandler grouperSessionHandler)
976       throws GrouperSessionException {
977     
978     Map<String, Object> debugMap = LOG.isDebugEnabled() ? new LinkedHashMap<String, Object>() : null;
979     if (LOG.isDebugEnabled()) {
980       debugMap.put("method", "callbackGrouperSession()");
981       if (grouperSession == null || grouperSession.getSubject() == null) {
982         debugMap.put("subject", "null");
983       } else {
984         debugMap.put("hash", grouperSession.hashCode());
985 
986         debugMap.put("subject", grouperSession.getSubject().getId());
987       }
988       logAddThreadLocal(debugMap, "start_");
989     }
990     Object ret = null;
991     try {
992       boolean needsToBeRemoved = false;
993       try {
994         //add to threadlocal
995         needsToBeRemoved = addStaticHibernateSession(grouperSession);
996         if (LOG.isDebugEnabled()) {
997           debugMap.put("needsToBeRemoved", needsToBeRemoved);
998           logAddThreadLocal(debugMap, "postAdd_");
999         }
1000         ret = grouperSessionHandler.callback(grouperSession);
1001     
1002       } finally {
1003         //remove from threadlocal
1004         if (needsToBeRemoved) {
1005           removeLastStaticGrouperSession(grouperSession);
1006         }
1007       }
1008 
1009     } finally {
1010       if (LOG.isDebugEnabled()) {
1011         logAddThreadLocal(debugMap, "end_");
1012         LOG.debug("Stack: " + GrouperUtil.stack());
1013         LOG.debug(debugMap);
1014       }
1015     }
1016 
1017     return ret;
1018     
1019   
1020   }
1021 
1022   /**
1023    * call this to send a callback for the root grouper session object. 
1024    * Any method in the inverse of
1025    * control can access the grouper session in a threadlocal
1026    * @param runAsRoot true to run as root, false to not run as root
1027    * @param grouperSessionHandler
1028    *          will get the callback
1029    * @return the object returned from the callback
1030    * @throws GrouperSessionException
1031    *           if there is a problem, will preserve runtime exceptions so they are
1032    *           thrown to the caller.  The GrouperSessionException wraps the underlying exception
1033    */
1034   public static Object internal_callbackRootGrouperSession(GrouperSessionHandler grouperSessionHandler)
1035       throws GrouperSessionException {
1036     return internal_callbackRootGrouperSession(true, grouperSessionHandler);
1037   }
1038   /**
1039    * call this to send a callback for the root grouper session object. 
1040    * Any method in the inverse of
1041    * control can access the grouper session in a threadlocal
1042    * 
1043    * @param grouperSessionHandler
1044    *          will get the callback
1045    * @return the object returned from the callback
1046    * @throws GrouperSessionException
1047    *           if there is a problem, will preserve runtime exceptions so they are
1048    *           thrown to the caller.  The GrouperSessionException wraps the underlying exception
1049    */
1050   public static Object internal_callbackRootGrouperSession(boolean runAsRoot, GrouperSessionHandler grouperSessionHandler)
1051       throws GrouperSessionException {
1052 
1053     // nevermind, dont run as root
1054     if (!runAsRoot) {
1055       return callbackGrouperSession(GrouperSession.staticGrouperSession(), grouperSessionHandler);
1056     }
1057     
1058     //this needs to run as root
1059     boolean startedGrouperSession = false;
1060     GrouperSession grouperSession = GrouperSession.staticGrouperSession(false);
1061     if (grouperSession == null) {
1062       grouperSession = GrouperSession.startRootSession(false);
1063       startedGrouperSession = true;
1064     }
1065     if (!PrivilegeHelper.isWheelOrRoot(grouperSession.getSubject())) {
1066       grouperSession = grouperSession.internal_getRootSession();
1067     }
1068     try {
1069       return callbackGrouperSession(grouperSession, grouperSessionHandler);
1070     } finally {
1071       if (startedGrouperSession) {
1072         GrouperSession.stopQuietly(grouperSession);
1073       }
1074     }
1075   }
1076 
1077   /**
1078    * set the threadlocal hibernate session
1079    * 
1080    * @param grouperSession
1081    * @return if it was added (if already last one, dont add again)
1082    */
1083   private static boolean addStaticHibernateSession(GrouperSession grouperSession) {
1084     List<GrouperSession> grouperSessionList = grouperSessionList();
1085     GrouperSession lastOne = grouperSessionList.size() == 0 ? null : grouperSessionList.get(grouperSessionList.size()-1);
1086     if (lastOne == grouperSession) {
1087       return false;
1088     }
1089     grouperSessionList.add(grouperSession);
1090     // cant have more than 60, something is wrong
1091     if (grouperSessionList.size() > 60) {
1092       grouperSessionList.clear();
1093       throw new RuntimeException(
1094           "There is probably a problem that there are 60 nested new GrouperSessions called!");
1095     }
1096     return true;
1097   }
1098 
1099   /**
1100    * get the threadlocal list of hibernate sessions (or create)
1101    * 
1102    * @return the set
1103    */
1104   private static List<GrouperSession> grouperSessionList() {
1105     List<GrouperSession> grouperSessionSet = staticSessions.get();
1106     if (grouperSessionSet == null) {
1107       // note the sessions are in order
1108       grouperSessionSet = new ArrayList<GrouperSession>();
1109       staticSessions.set(grouperSessionSet);
1110     }
1111     return grouperSessionSet;
1112   }
1113 
1114   /**
1115    * this should remove the last grouper session which should be the same as
1116    * the one passed in
1117    * 
1118    * @param grouperSession should match the last group session
1119    */
1120   private static void removeLastStaticGrouperSession(GrouperSession grouperSession) {
1121     //this one better be at the end of the list
1122     List<GrouperSession> grouperSessionList = grouperSessionList();
1123     int size = grouperSessionList.size();
1124     if (size == 0) {
1125       throw new RuntimeException("Supposed to remove a session from stack, but stack is empty");
1126     }
1127     GrouperSession lastOne = grouperSessionList.get(size-1);
1128     //the reference must be the same
1129     if (lastOne != grouperSession) {
1130       //i guess just clear it out
1131       grouperSessionList.clear();
1132       throw new RuntimeException("Illegal state, the grouperSession threadlocal stack is out of sync!");
1133     }
1134     grouperSessionList.remove(grouperSession);
1135   }
1136 
1137   /**
1138    * get the threadlocal grouper session. access this through inverse of
1139    * control.  this should be called by internal grouper methods which need the
1140    * grouper session
1141    * 
1142    * @return the grouper session or null if none there
1143    */
1144   public static GrouperSession staticGrouperSession() {
1145     return staticGrouperSession(true);
1146   }
1147   
1148   /**
1149    * clear the threadlocal grouper session (dont really need to call this, just
1150    * stop the session, but this is here for testing)
1151    */
1152   public static void clearGrouperSession() {
1153     staticGrouperSession.remove();
1154   }
1155   
1156   /**
1157    * clear the threadlocal grouper sessions (dont really need to call this, just
1158    * stop the session, but this is here for testing)
1159    */
1160   public static void clearGrouperSessions() {
1161     staticSessions.remove();
1162   }
1163   
1164   /**
1165    * get the threadlocal grouper session. access this through inverse of
1166    * control.  this should be called by internal grouper methods which need the
1167    * grouper session
1168    * @param exceptionOnNull true if exception when there is none there
1169    * 
1170    * @return the grouper session or null if none there
1171    * @throws IllegalStateException if no sessions available
1172    */
1173   public static GrouperSession staticGrouperSession(boolean exceptionOnNull) 
1174       throws IllegalStateException {
1175 
1176     //first look at the list of threadlocals
1177     List<GrouperSession> grouperSessionList = grouperSessionList();
1178     int size = grouperSessionList.size();
1179     String error = "There is no open GrouperSession detected.  Make sure " +
1180         "to start a grouper session (e.g. GrouperSession.startRootSession() if you want to use a root session ) before calling this method";
1181     GrouperSession grouperSession = null;
1182     if (size == 0) {
1183       //if nothing in the threadlocal list, then use the last one
1184       //started (and added)
1185       grouperSession = staticGrouperSession.get();
1186       
1187     } else {
1188       // get the last index, return null if session closed
1189       grouperSession = grouperSessionList.get(size-1);
1190     }
1191 
1192     if (grouperSession != null && grouperSession.subject == null) {
1193       grouperSession = null;
1194     }
1195     
1196     if (exceptionOnNull && grouperSession == null) {
1197       throw new IllegalStateException(error);
1198       }
1199       return grouperSession;
1200     }
1201   
1202 }