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   * 
18   */
19  package edu.internet2.middleware.grouper.hibernate;
20  
21  import java.sql.SQLException;
22  import java.sql.Savepoint;
23  import java.util.LinkedHashMap;
24  import java.util.LinkedHashSet;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import org.apache.commons.lang.StringUtils;
30  import org.apache.commons.lang.exception.ExceptionUtils;
31  import org.apache.commons.logging.Log;
32  import org.hibernate.HibernateException;
33  import org.hibernate.Query;
34  import org.hibernate.SQLQuery;
35  import org.hibernate.Session;
36  import org.hibernate.StaleObjectStateException;
37  import org.hibernate.StaleStateException;
38  import org.hibernate.Transaction;
39  import org.hibernate.internal.SessionImpl;
40  import org.hibernate.resource.transaction.spi.TransactionStatus;
41  import org.hibernate.type.StandardBasicTypes;
42  import org.hibernate.type.Type;
43  
44  import edu.internet2.middleware.grouper.cfg.GrouperConfig;
45  import edu.internet2.middleware.grouper.ddl.GrouperDdlUtils;
46  import edu.internet2.middleware.grouper.exception.GrouperReadonlyException;
47  import edu.internet2.middleware.grouper.exception.GrouperStaleObjectStateException;
48  import edu.internet2.middleware.grouper.exception.GrouperStaleStateException;
49  import edu.internet2.middleware.grouper.exception.GrouperValidationException;
50  import edu.internet2.middleware.grouper.hooks.logic.HookVeto;
51  import edu.internet2.middleware.grouper.internal.dao.GrouperDAOException;
52  import edu.internet2.middleware.grouper.misc.GrouperDAOFactory;
53  import edu.internet2.middleware.grouper.util.GrouperUtil;
54  
55  /**
56   * <pre>
57   * Hibernate helper class.  These are kept in a threadlocal to keep 
58   * transactions going smoothly.  If you are in a nested callback situation,
59   * and in the same transaction, then the HibernateSession instance will be
60   * different, but the underlying Session (from hibernate) object will be the same.  
61   * To get an instanceof HibernateSession, use the callbackHibernateSession
62   * inverse of control method.
63   * 
64   * </pre>
65   * 
66   * @author mchyzer
67   * 
68   */
69  public class HibernateSession {
70  
71    /**
72     * error message when readonly mode
73     */
74    private static final String READONLY_ERROR = "Grouper is in readonly mode (perhaps due to maintenance), you cannot perform an operation which changes the data!";
75  
76    /** logger */
77    private static final Log LOG = GrouperUtil.getLog(HibernateSession.class);
78  
79    /**
80     * 
81     * @return the class
82     */
83    public ByObject byObject(){
84      return new ByObject(this);
85    }
86    
87    /** save point count for testing */
88    static int savePointCount = 0;
89    
90    /** threadlocal to store if we are in readonly mode */
91    private static ThreadLocal<Boolean> threadlocalReadonly = new ThreadLocal<Boolean>();
92      
93    /**
94     * @return the internal_threadlocalReadonly
95     */
96    public static Boolean internal_retrieveThreadlocalReadonly() {
97      return threadlocalReadonly.get();
98    }
99  
100   
101   /**
102    * @param internal_threadlocalReadonly the internal_threadlocalReadonly to set
103    */
104   public static void internal_assignThreadlocalReadonly(Boolean internal_threadlocalReadonly) {
105     if (internal_threadlocalReadonly == null) {
106       threadlocalReadonly.remove();
107     } else {
108       threadlocalReadonly.set(internal_threadlocalReadonly);
109     }
110   }
111 
112   /**
113    * if readonly by threadlocal or config param
114    * @return true if readonly
115    */
116   public static boolean isReadonlyMode() {
117     if (GrouperConfig.retrieveConfig().propertyValueBoolean("grouper.api.readonly", false)) {
118       return true;
119     }
120     
121     Boolean threadlocalBoolean = threadlocalReadonly.get();
122     
123     return threadlocalBoolean != null && threadlocalBoolean;
124     
125   }
126 
127   /**
128    * 
129    * assign that grouper is in readonly mode, make sure to call clear in a finally block
130    */
131   public static void threadLocalReadonlyAssign() {
132     threadlocalReadonly.set(true);
133   }
134 
135   /**
136    * in finally block call this to not make grouper readonly anymore
137    */
138   public static void threadLocalReadonlyClear() {
139     threadlocalReadonly.remove();
140   }
141   
142   /**
143    * construct a hibernate session based on existing hibernate session (if
144    * applicable), and a transaction type. If these conflict, then throw grouper
145    * dao exception exception
146    * 
147    * @param parentHibernateSession is the parent session
148    *          if exists
149    * @param grouperTransactionType
150    *          that this was created with
151    * @throws GrouperDAOException
152    *           if something conflicts (e.g. read/write if exists, and exists is
153    *           readonly
154    */
155   @SuppressWarnings("deprecation")
156   private HibernateSession../../../../../edu/internet2/middleware/grouper/hibernate/HibernateSession.html#HibernateSession">HibernateSession(HibernateSession parentHibernateSession,
157       GrouperTransactionType grouperTransactionType) throws GrouperDAOException {
158 
159     Map<String, Object> debugMap = LOG.isDebugEnabled() ? new LinkedHashMap<String, Object>() : null;
160     GrouperTransactionType originalGrouperTransactionType = grouperTransactionType;
161     try {
162 
163       if (LOG.isDebugEnabled()) {
164         debugMap.put("grouperTransactionType", grouperTransactionType);
165       }
166       
167       boolean okToUseHibernate = GrouperDdlUtils.okToUseHibernate();
168       
169       if (LOG.isDebugEnabled()) {
170         debugMap.put("okToUseHibernate", okToUseHibernate);
171       }
172         
173       if (!okToUseHibernate) {
174         if (GrouperConfig.retrieveConfig().propertyValueBoolean("ddlutils.failIfNotRightVersion", true)) {
175           throw new RuntimeException("Database schema ddl is not up to date, or has issues, check logs and config ddl in grouper.properties and run: gsh -registry -check");
176         }
177       }
178       
179       if (grouperTransactionType != null && grouperTransactionType.isTransactional()) {
180         //if readonly, then dont allow read/write transactions
181         boolean readonlyMode = isReadonlyMode();
182   
183         if (LOG.isDebugEnabled()) {
184           debugMap.put("readonlyMode", readonlyMode);
185         }
186           
187         if (readonlyMode) {
188           if (grouperTransactionType != null && grouperTransactionType.isTransactional()) {
189             grouperTransactionType = GrouperTransactionType.READONLY_OR_USE_EXISTING;
190             
191             if (LOG.isDebugEnabled() && grouperTransactionType != originalGrouperTransactionType) {
192               debugMap.put("readonlyGrouperTransactionTypeChangedTo", grouperTransactionType);
193             }
194             originalGrouperTransactionType = grouperTransactionType;
195           }
196         }
197       }
198       
199       boolean parentSessionExists = parentHibernateSession != null;
200       if (LOG.isDebugEnabled()) {
201         debugMap.put("parentSessionExists", parentSessionExists);
202       }
203       
204       this.immediateGrouperTransactionTypeDeclared = grouperTransactionType;
205       
206       //if parent is none, then make sure this is a new transaction (not dependent on none)
207       if (parentSessionExists) {
208         
209         this.cachingEnabled = parentHibernateSession.cachingEnabled;
210   
211       }
212   
213       //if parent is none, then make sure this is a new transaction (not dependent on none)
214       if (parentSessionExists && !grouperTransactionType.isNewAutonomous() && 
215           parentHibernateSession.activeHibernateSession().immediateGrouperTransactionTypeUsed.isTransactional()) {
216   
217         //if there is a parent, then it is inherited.  even if not autonomous, only inherit if not parent of none
218         this.parentSession = parentHibernateSession;
219       
220         //make sure the transaction types jive with each other
221         this.immediateGrouperTransactionTypeDeclared.checkCompatibility(
222             this.parentSession.getGrouperTransactionType());
223       }
224       
225       boolean newHibernateSession = this.isNewHibernateSession();
226       
227       if (LOG.isDebugEnabled()) {
228         debugMap.put("newHibernateSession", newHibernateSession);
229       }
230       
231       if (newHibernateSession) {
232         
233         if (grouperTransactionType == null) {
234           throw new NullPointerException("transaction type is null in hibernate session");
235         }
236         
237         this.immediateGrouperTransactionTypeUsed = grouperTransactionType
238             .grouperTransactionTypeToUse();
239   
240         // need a hibernate session (note, if none, then we dont need a session?)
241         if (!GrouperTransactionType.NONE.equals(grouperTransactionType)) {
242           this.immediateSession = GrouperDAOFactory.getFactory().getSession();
243         }
244 
245         if (LOG.isDebugEnabled()) {
246           debugMap.put("immediateGrouperTransactionTypeUsed", this.immediateGrouperTransactionTypeUsed);
247           debugMap.put("immediateGrouperTransactionTypeReadonly", this.immediateGrouperTransactionTypeUsed.isReadonly());
248         }
249 
250         // if not readonly, declare a transaction
251         if (!this.immediateGrouperTransactionTypeUsed.isReadonly()) {
252           if (LOG.isDebugEnabled()) {
253             debugMap.put("beginTransaction", "true");
254           }
255           this.immediateTransaction = this.immediateSession.beginTransaction();
256   
257           String useSavepointsString = GrouperConfig.retrieveConfig().propertyValueString("jdbc.useSavePoints");
258           boolean useSavepoints;
259           if (StringUtils.isBlank(useSavepointsString)) {
260             useSavepoints = !GrouperDdlUtils.isHsql();
261           } else {
262             useSavepoints = GrouperUtil.booleanValue(useSavepointsString);
263           }
264 
265           if (LOG.isDebugEnabled()) {
266             debugMap.put("useSavepoints", useSavepoints);
267           }
268           
269           if (useSavepoints && (parentSessionExists   // && this.activeHibernateSession().isTransactionActive()  && !this.activeHibernateSession().isReadonly() 
270               || GrouperConfig.retrieveConfig().propertyValueBoolean("jdbc.useSavePointsOnAllNewTransactions", false))) {
271             try {
272               this.savepoint = ((SessionImpl)this.activeHibernateSession().getSession()).connection().setSavepoint();
273               savePointCount++;
274             } catch (SQLException sqle) {
275               throw new RuntimeException("Problem setting save point for transaction type: " 
276                   + grouperTransactionType, sqle);
277             }
278           } else if (GrouperDdlUtils.isHsql() && parentSessionExists) {
279             //do this for tests...
280             savePointCount++;
281           }
282         }
283       }
284       
285       
286       addStaticHibernateSession(this);
287     } finally {
288       if (LOG.isDebugEnabled()) {
289         debugMap.put("hibernateSession", this.toString());
290         LOG.debug(GrouperUtil.stack());
291         LOG.debug(GrouperUtil.mapToString(debugMap));
292       }
293     }
294   }
295 
296   /** hibernate session object of parent if nested, or null */
297   private HibernateSession parentSession = null;
298 
299   /**
300    * hibernate session object can be accessed by user, if there is a parent,
301    * this will be null.
302    */
303   private Session immediateSession = null;
304 
305   /**
306    * if read/write, this will exist, though the user cant access directly. if
307    * there is a parent, this will ne null
308    */
309   private Transaction immediateTransaction = null;
310 
311   /**
312    * if read/write, postgres needs to rollback to save point if nested transaction
313    */
314   private Savepoint savepoint = null;
315   
316   /**
317    * the transaction type this was setup as. note, if the type is new, then it
318    * might change from what it was declared as...
319    */
320   private GrouperTransactionType immediateGrouperTransactionTypeUsed = null;
321 
322   /**
323    * the transaction type this was setup as. this is the one declared in
324    * callback
325    */
326   private GrouperTransactionType immediateGrouperTransactionTypeDeclared = null;
327 
328   /**
329    * provide ability to turn off all caching for this session
330    */
331   private boolean cachingEnabled = true;
332   
333   /**
334    * provide ability to turn off all caching for this session
335    * @return the enabledCaching
336    */
337   public boolean isCachingEnabled() {
338     return this.cachingEnabled;
339   }
340 
341   /**
342    * provide ability to turn off all caching for this session
343    * note, you can also use a try/finally with HibUtils.assignDisallowCacheThreadLocal() and 
344    * HibUtils.clearDisallowCacheThreadLocal()
345    * @param enabledCaching1 the enabledCaching to set
346    */
347   public void setCachingEnabled(boolean enabledCaching1) {
348     this.cachingEnabled = enabledCaching1;
349   }
350 
351   /**
352    * store the hib2 connection in thread local so other classes can get it
353    */
354   private static ThreadLocal<Set<HibernateSession>> staticSessions = new ThreadLocal<Set<HibernateSession>>();
355 
356   /**
357    * this is for internal purposes only, dont use this unless you know what you are doing
358    * @return the set of hibernate sessions
359    */
360   public static Set<HibernateSession> _internal_staticSessions() {
361     return getHibernateSessionSet();
362   }
363   
364   /**
365    * get the threadlocal set of hibernate sessions (or create)
366    * 
367    * @return the set
368    */
369   private static Set<HibernateSession> getHibernateSessionSet() {
370     Set<HibernateSession> hibSet = staticSessions.get();
371     if (hibSet == null) {
372       // note the sessions are in order
373       hibSet = new LinkedHashSet<HibernateSession>();
374       staticSessions.set(hibSet);
375     }
376     return hibSet;
377   }
378 
379   /**
380    * this should remove the last hibernate session which should be the same as
381    * the one passed in
382    * 
383    * @param hibernateSession
384    */
385   private static void removeStaticHibernateSession(HibernateSession hibernateSession) {
386     getHibernateSessionSet().remove(hibernateSession);
387   }
388 
389   /**
390    * call this at the end of requests to make sure everything is cleared out or
391    * call periodically...
392    */
393   public static void resetAllThreadLocals() {
394     getHibernateSessionSet().clear();
395   }
396 
397   /**
398    * set the threadlocal hibernate session
399    * 
400    * @param hibernateSession
401    */
402   private static void addStaticHibernateSession(HibernateSession hibernateSession) {
403     Set<HibernateSession> hibSet = getHibernateSessionSet();
404     hibSet.add(hibernateSession);
405     // cant have more than 20, something is wrong
406     if (hibSet.size() > 20) {
407       hibSet.clear();
408       throw new RuntimeException(
409           "There is probably a problem that there are 20 nested new HibernateSessions called!");
410     }
411   }
412 
413   /**
414    * get the current hibernate session.  dont call this unless you know what you are doing
415    * @return the current hibernate session
416    */
417   public static HibernateSession _internal_hibernateSession() {
418     return staticHibernateSession();
419   }
420   
421   /**
422    * get the threadlocal hibernate session. access this through inverse of
423    * control (granted you wont get the exact same instance...)
424    * 
425    * @return the hibernate session or null if none there
426    */
427   private static HibernateSession staticHibernateSession() {
428     Set<HibernateSession> hibSet = getHibernateSessionSet();
429     int size = hibSet.size();
430     if (size == 0) {
431       return null;
432     }
433     // get the second to last index
434     return (HibernateSession) GrouperUtil.get(hibSet, size - 1);
435   }
436 
437   /**
438    * close all sessions, but dont throw errors, based on throwable
439    * @param t 
440    */
441   public static void _internal_closeAllHibernateSessions(Throwable t) {
442     //if there is an exception, close all sessions
443     try {
444       for (HibernateSession hibernateSession : HibernateSession._internal_staticSessions()) {
445         try {
446           HibernateSession._internal_hibernateSessionCatch(hibernateSession, t);
447         } catch (Exception e) {
448           //swallow I guess
449           LOG.debug("Error handling hibernate error", e);
450         } finally {
451           try {
452             HibernateSession._internal_hibernateSessionFinally(hibernateSession);
453           } catch (Throwable t3) {
454             LOG.debug("Error in finally for hibernate session", t3);
455           }
456         }
457       }
458     } catch (Throwable t2) {
459       LOG.debug("Problem closing sessions", t2);
460     }
461   }
462   
463   /**
464    * dont call this method unless you know what you are doing
465    * @param grouperTransactionType
466    * @return the hiberate session for internal purposes
467    * @throws GrouperDAOException
468    */
469   public static HibernateSession _internal_hibernateSession(GrouperTransactionType grouperTransactionType) throws GrouperDAOException {
470     HibernateSessionbernateSession.html#HibernateSession">HibernateSession hibernateSession = new HibernateSession(staticHibernateSession(), grouperTransactionType);
471     return hibernateSession;
472   }
473   
474   /**
475    * end a hibernate session.  dont call this unless you know what you are doing
476    * @param hibernateSession 
477    * @throws SQLException 
478    */
479   @SuppressWarnings("deprecation")
480   public static void _internal_hibernateSessionEnd(HibernateSession hibernateSession) throws SQLException {
481     
482     //since we have long running transactions, we need to flush our work,
483     //and disassociate objects with the session...
484     Session session = hibernateSession.activeHibernateSession().immediateSession;
485 
486     //if we are readonly, and we have work, then that is bad
487     if (hibernateSession.isReadonly() 
488         && session != null && session.isDirty()) {
489       ((SessionImpl)session).connection().rollback();
490       //when i retrieve a bunch of fields, this doesnt work.  why???
491       //throw new RuntimeException("Hibernate session is readonly, but some committable work was done!");
492     }
493     
494     // maybe we didnt commit. if new session, and no exception, and not
495     // committed or rolledback,
496     // then commit.
497     if (hibernateSession.isNewHibernateSession() && !hibernateSession.isReadonly()
498         && hibernateSession.immediateTransaction.getStatus().isOneOf(TransactionStatus.ACTIVE)) {
499 
500       LOG.debug("endTransactionAutoCommit");
501       
502       assertNotGrouperReadonly();
503       
504       hibernateSession.immediateTransaction.commit();
505 
506     } else {
507       //only do this if a nested transaction
508       
509       if (session != null && !hibernateSession.isNewHibernateSession()) {
510         //put all the queries on the wire
511         session.flush();
512 
513         //clear out session to avoid duplicate objects in session
514         session.clear();
515       }
516     }
517     
518   }
519 
520   /**
521    * make sure not readonly mode
522    */
523   public static void assertNotGrouperReadonly() {
524     if (GrouperConfig.retrieveConfig().propertyValueBoolean("grouper.api.readonly", false)) {
525       throw new GrouperReadonlyException(READONLY_ERROR);
526     }
527   }
528 
529   /**
530    * catch and handle an exception while working with hibernate session.  Dont call this if you dont know what you are doing.
531    * @param hibernateSession
532    * @param e 
533    * @throws GrouperDAOException 
534    */
535   @SuppressWarnings("deprecation")
536   public static void _internal_hibernateSessionCatch(HibernateSession hibernateSession, Throwable e) throws GrouperDAOException {
537 
538     try {
539       //if there was a save point, rollback (since postgres doesnt like a failed query not rolled back)
540       if (hibernateSession != null && hibernateSession.savepoint != null) {
541         try {
542           ((SessionImpl)hibernateSession.activeHibernateSession().getSession()).connection().rollback(hibernateSession.savepoint);
543         } catch (SQLException sqle) {
544           throw new RuntimeException("Problem rolling back savepoint", sqle);
545         }
546       }
547     } catch (RuntimeException re) {
548       //hmmm, dont die on a rollback, but put it in the original exception
549       if (!GrouperUtil.injectInException(e, "Exception rolling back savepoint in exception catch: " + ExceptionUtils.getFullStackTrace(re))) {
550         LOG.error("Error", e);
551       }
552     }    
553     
554     try {
555       // maybe we didnt rollback. if new session, and exception, and not
556       // committed or rolledback,
557       // then rollback.
558       //CH 20080220: should we always rollback?  or if not rollback, flush and clear?
559       if (hibernateSession != null && hibernateSession.isNewHibernateSession() && !hibernateSession.isReadonly()) {
560         if (hibernateSession.immediateTransaction.getStatus().isOneOf(TransactionStatus.ACTIVE)) {
561           LOG.debug("endTransactionRollback");
562           hibernateSession.immediateTransaction.rollback();
563         }
564       }
565     } catch (RuntimeException re) {
566       //hmmm, dont die on a rollback, but put it in the original exception
567       if (!GrouperUtil.injectInException(e, "Exception rolling back in exception catch: " + ExceptionUtils.getFullStackTrace(re))) {
568         LOG.error("Error", e);
569       }
570     }
571     
572     //postgres logs in nextException, so see if there is one there
573     GrouperUtil.logErrorNextException(LOG, e, 100);
574     
575     String errorString = "Problem in HibernateSession: " + hibernateSession;
576     // rethrow
577     if (e instanceof GrouperDAOException) {
578       if (!GrouperUtil.injectInException(e, errorString)) {
579         LOG.error(errorString);
580       }
581       throw (GrouperDAOException) e;
582     }
583     if (e instanceof StaleObjectStateException) {
584       throw new GrouperStaleObjectStateException(errorString, e);
585     }
586     if (e instanceof StaleStateException) {
587       throw new GrouperStaleStateException(errorString, e);
588     }
589     // if hibernate exception, repackage
590     if (e instanceof HibernateException) {
591       throw new GrouperDAOException(errorString, e);
592     }
593     if (e instanceof HookVeto) {
594       throw (HookVeto)e;
595     }
596     if (e instanceof GrouperValidationException) {
597       throw (GrouperValidationException)e;
598     }
599     // if runtime, then rethrow
600     if (e instanceof RuntimeException) {
601       if (!GrouperUtil.injectInException(e, errorString)) {
602         LOG.error(errorString);
603       }
604       throw (RuntimeException) e;
605     }
606     // if exception and not handled, convert to GrouperDaoException
607     throw new GrouperDAOException(errorString, e);
608 
609   }
610   
611   /**
612    * finally block from hibernate session (dont call unless you know what you are doing
613    * 
614    * @param hibernateSession
615    * @return if closed
616    */
617   public static boolean _internal_hibernateSessionFinally(HibernateSession hibernateSession) {
618     if (hibernateSession != null) {
619       // take out of threadlocal stack
620       removeStaticHibernateSession(hibernateSession);
621       // take out of threadlocal if supposed to
622       if (hibernateSession.isNewHibernateSession()) {
623         // we should close the hibernate session if we opened it, and if not
624         // already closed
625         // transaction is already closed...
626         return closeSessionIfNotClosed(hibernateSession.immediateSession);
627       }
628     }
629     return false;
630   }
631   
632   /**
633    * call this to send a callback for the hibernate session object. cant use
634    * inverse of control for this since it runs it
635    * 
636    * @param grouperTransactionType
637    *          is enum of how the transaction should work.
638    * @param auditControl WILL_AUDIT if caller will create an audit record, WILL_NOT_AUDIT if not
639    * @param hibernateHandler
640    *          will get the callback
641    * @return the object returned from the callback
642    * @throws GrouperDAOException
643    *           if there is a problem, will preserve runtime exceptions so they are
644    *           thrown to the caller
645    */
646   public static Object callbackHibernateSession(
647       GrouperTransactionType grouperTransactionType, AuditControl auditControl, HibernateHandler hibernateHandler)
648       throws GrouperDAOException {
649     
650     Map<String, Object> debugMap = LOG.isDebugEnabled() ? new LinkedHashMap<String, Object>() : null;
651     
652     Object ret = null;
653     HibernateSession hibernateSession = null;
654 
655     try {
656       
657       if (LOG.isDebugEnabled()) {
658         debugMap.put("grouperTransactionType", grouperTransactionType == null ? null : grouperTransactionType.name());
659         debugMap.put("auditControl", auditControl == null ? null : auditControl);
660       }
661       
662       hibernateSession = _internal_hibernateSession(grouperTransactionType);
663 
664       if (LOG.isDebugEnabled()) {
665 
666         debugMap.put("hibernateSession", hibernateSession.toString());
667 
668         StringBuilder sessionsThreadLocal = new StringBuilder();
669         boolean first = true;
670         for (HibernateSession theHibernateSession : getHibernateSessionSet()) {
671           if (!first) {
672             sessionsThreadLocal.append(", ");
673           }
674           sessionsThreadLocal.append(Integer.toHexString(theHibernateSession.hashCode()));
675           first = false;
676         }
677         debugMap.put("hibernateSessionsInThreadLocal", sessionsThreadLocal.toString());
678       }
679       
680       HibernateHandlerBeanandlerBean.html#HibernateHandlerBean">HibernateHandlerBean hibernateHandlerBean = new HibernateHandlerBean();
681       boolean willCreateAudit = AuditControl.WILL_AUDIT.equals(auditControl);
682       if (LOG.isDebugEnabled()) {
683         debugMap.put("willCreateAudit", willCreateAudit);
684       }
685       hibernateHandlerBean.setCallerWillCreateAudit(willCreateAudit);
686 
687       //see if the caller will audit.  if not, then it is up to this call
688       boolean callerWillAudit = GrouperContext.contextExistsInner();
689 
690       //create a new context
691       boolean createdContext = willCreateAudit ? GrouperContext.createNewInnerContextIfNotExist() : false;
692 
693       if (LOG.isDebugEnabled()) {
694         debugMap.put("createdContext", createdContext);
695       }
696 
697       try {
698         hibernateHandlerBean.setCallerWillCreateAudit(callerWillAudit);
699         hibernateHandlerBean.setNewContext(createdContext);
700         
701         hibernateHandlerBean.setHibernateSession(hibernateSession);
702         
703         ret = hibernateHandler.callback(hibernateHandlerBean);
704       } finally {
705         //if we created a context, then remove it
706         if (createdContext) {
707           GrouperContext.deleteInnerContext();
708         }
709       }
710       _internal_hibernateSessionEnd(hibernateSession);
711 
712     } catch (Throwable e) {
713       _internal_hibernateSessionCatch(hibernateSession, e);
714     } finally {
715       boolean closed = _internal_hibernateSessionFinally(hibernateSession);
716       if (LOG.isDebugEnabled()) {
717         debugMap.put("closedSession", closed);
718         LOG.debug(GrouperUtil.stack());
719         LOG.debug(GrouperUtil.mapToString(debugMap));
720       }
721     }
722     return ret;
723 
724   }
725 
726   /**
727    * do a hql query with proper error handling and in an enclosing transaction
728    * (if applicable), or a new one if not
729    * @return the class
730    */
731   public static ByHqlStatic byHqlStatic() {
732     return new ByHqlStatic();
733   }
734 
735   /**
736    * do a sql query with proper error handling and in an enclosing transaction
737    * (if applicable), or a new one if not
738    * @return the class
739    */
740   public static BySqlStatic bySqlStatic() {
741     return new BySqlStatic();
742   }
743 
744   /**
745    * do a criteria query with proper error handling and in an enclosing transaction
746    * (if applicable), or a new one if not
747    * @return the class
748    */
749   public static ByCriteriaStatic byCriteriaStatic() {
750     return new ByCriteriaStatic();
751   }
752 
753   /**
754    * do an object operation 
755    * with proper error handling and in an enclosing transaction
756    * (if applicable), or a new one if not
757    * @return the class
758    */
759   public static ByObjectStatic byObjectStatic() {
760     return new ByObjectStatic();
761   }
762 
763   /**
764    * Close session. Do not throw or log error. This is good in a finally block
765    * to make sure DB connections are returned.
766    * 
767    * @param session
768    *          is hibernate session to close
769    * @return if closed
770    */
771   private static boolean closeSessionIfNotClosed(Session session) {
772 
773     if (session != null) {
774 
775       try {
776         // if already closed (not sure why), just ignore
777         if (session.isConnected() && session.isOpen()) {
778           session.close();
779           return true;
780         }
781       } catch (Exception e) {
782         // swallow the exception... no throwing, no logging
783       }
784     }
785     return false;
786   }
787 
788   /**
789    * descriptive toString for error handling
790    */
791   @Override
792   public String toString() {
793     try {
794       return "HibernateSession (" + Integer.toHexString(this.hashCode()) + "): " 
795           + (this.isNewHibernateSession() ? "new" : "notNew") + ", " 
796           + (this.isReadonly() ? "readonly" : "notReadonly") + ", "
797           + (this.getGrouperTransactionType() == null ? null : this.getGrouperTransactionType().name()) + ", "
798           + (this.isTransactionActive() ? "activeTransaction" : "notActiveTransaction" ) 
799           + ", session (" + (this.getSession() == null ? null : Integer.toHexString(this.getSession().hashCode())) + ")"
800           ;
801     } catch (NullPointerException npe) {
802       throw npe;
803     }
804   }
805 
806   /**
807    * hibernate session object can be accessed by user.
808    * 
809    * @return the session
810    */
811   public Session getSession() {
812     return this.activeHibernateSession().immediateSession;
813   }
814 
815   /**
816    * misc actions for hibernate session
817    * @return the class
818    */
819   public HibernateMisc misc(){
820     return new HibernateMisc(this);
821   }
822 
823   /**
824    * hql action for hibernate
825    * @return the byhql
826    */
827   public ByHql byHql(){
828     return new ByHql(this);
829   }
830 
831   /**
832    * hql action for hibernate
833    * @return the byhql
834    */
835   public BySql bySql(){
836     return new BySql(this);
837   }
838 
839   /**
840    * see if this is a new hibernate session
841    * 
842    * @return the newHibernateSession
843    */
844   public boolean isNewHibernateSession() {
845     // if no parent, then it is new, unless it is a new autonomous transaction
846     //in which case there will be no parent...
847     return this.parentSession == null;
848   }
849 
850   /**
851    * if this is readonly (based on this declaration or underlying)
852    * 
853    * @return the readonly
854    */
855   public boolean isReadonly() {
856     return this.getGrouperTransactionType().isReadonly();
857   }
858 
859   /**
860    * get this object if new, or underlying if exist
861    * 
862    * @return the hibernate session
863    */
864   private HibernateSession activeHibernateSession() {
865     return this.parentSession == null ? this : this.parentSession
866         .activeHibernateSession();
867   }
868 
869   /**
870    * this will return the underlying (if exist) transaction type, and if not,
871    * then the one this was constructed with
872    * 
873    * @return the hibernate transaction type
874    */
875   public GrouperTransactionType getGrouperTransactionType() {
876     return this.activeHibernateSession().immediateGrouperTransactionTypeUsed;
877   }
878 
879   /**
880    * commit (perhaps, depending on type)
881    * @param grouperCommitType is type of commit
882    * @return true if committed, false if not
883    */
884   public boolean commit(GrouperCommitType grouperCommitType) {
885 
886     assertNotGrouperReadonly();
887     
888     switch (grouperCommitType) {
889       case COMMIT_IF_NEW_TRANSACTION:
890         if (this.isNewHibernateSession()) {
891           LOG.debug("endTransactionCommitIfNew");
892           this.activeHibernateSession().immediateTransaction.commit();
893           this.activeHibernateSession().savepoint = null;
894           return true;
895         }
896         break;
897       case COMMIT_NOW:
898         LOG.debug("endTransactionCommitNow");
899         this.activeHibernateSession().immediateTransaction.commit();
900         this.activeHibernateSession().savepoint = null;
901         return true;
902     }
903     return false;
904   }
905 
906   /**
907    * see if tx is active (not committed or rolled back, see Hibernate transaction
908    * if there is no transaction, it will return false
909    * @return true if active, false if not or not transaction
910    */
911   public boolean isTransactionActive() {
912     if (this.isReadonly()) {
913       return false;
914     }
915     return this.activeHibernateSession().immediateTransaction == null ? false : this
916         .activeHibernateSession().immediateTransaction.getStatus().isOneOf(TransactionStatus.ACTIVE);
917   }
918 
919   /**
920    * rollback (perhaps, depending on type)
921    * @param grouperRollbackType is type of rollback
922    * @return true if rollback, false if not
923    */
924   public boolean rollback(GrouperRollbackType grouperRollbackType) {
925     switch (grouperRollbackType) {
926       case ROLLBACK_IF_NEW_TRANSACTION:
927         if (this.isNewHibernateSession()) {
928           LOG.debug("endTransactionRollbackIfNew");
929           this.activeHibernateSession().immediateTransaction.rollback();
930           return true;
931         }
932         break;
933       case ROLLBACK_NOW:
934         if (this.activeHibernateSession() != null && this.activeHibernateSession().immediateTransaction != null) { 
935           LOG.debug("endTransactionRollbackNow");
936           this.activeHibernateSession().immediateTransaction.rollback();
937         }
938         return true;
939     }
940     return false;
941   }
942 
943   /**
944    * Query hibernate objects by sql, get the list, and evict
945    * @param query SQL query, but have curly brackets per hibernate spec, e.g.
946    * SELECT {cat.*} FROM CAT {cat} WHERE cat.name='barney'
947    * @param aliasOfObject is a string or array of strings of alias of the object in 
948    * the SQL e.g. is SELECT {cat.*} FROM ... the alias is "cat"
949    * @param types is the Class or Class[] of type of object(s) returned
950    * @param params prepared statement params (null, Object, Object[], or List of Objects)
951    * @param paramTypes prepared statement types(null, Type, Type[], or List of Types)
952    * @return the array of objects
953    */
954   List retrieveListBySql(String query, String aliasOfObject, Class types,
955       Object params, Object paramTypes) {
956     
957     List list = null;
958 
959     Query hibQuery = retrieveQueryBySql(query, aliasOfObject, types, params, paramTypes);
960     
961     list = hibQuery.list();
962     if (list != null) {
963       //make sure objects are not attached to hib session
964       //only do this if hibernate objects
965       if (types != null) {
966         HibUtils.evict(this, list, true);
967       }
968     }
969     return list;
970   }
971 
972   /**
973    * Query hibernate objects by sql
974    * @param query SQL query, but have curly brackets per hibernate spec, e.g.
975    * SELECT {cat.*} FROM CAT {cat} WHERE cat.name='barney'
976    * @param aliasOfObject is a string or array of strings of alias of the object in 
977    * the SQL e.g. is SELECT {cat.*} FROM ... the alias is "cat"
978    * @param types is the Class or Class[] of type of object(s) returned
979    * @param params prepared statement params (null, Object, Object[], or List of Objects)
980    * @param paramTypes prepared statement types(null, Type, Type[], or List of Types)
981    * @return the array of objects
982    */
983   @SuppressWarnings("unchecked") Query retrieveQueryBySql(String query, String aliasOfObject, Class types,
984       Object params, Object paramTypes) {
985 
986     Query hibQuery = null;
987 
988     try {
989 
990       hibQuery = this.getSession().createSQLQuery(query);
991 
992       if (aliasOfObject != null && types != null) {
993         hibQuery = ((SQLQuery)hibQuery).addEntity(aliasOfObject, types);
994       } else if (types != null) {
995         //if no entity then just get the list of object[] or objects
996         //CH 061105 adding support for native queries for lists of object[]'s or objects
997         hibQuery = ((SQLQuery)hibQuery).addEntity(types);
998       }
999 
1000       attachParams(hibQuery, params, paramTypes);
1001 
1002       
1003     } catch (Throwable he) {
1004       throw new RuntimeException("Error querying for array of objects, query: " + query + ", "
1005           + HibUtils.paramsToString(params, paramTypes), he);
1006     }
1007 
1008     return hibQuery;
1009 
1010   }
1011 
1012 
1013   /**
1014    * Attach params for a prepared statement
1015    * @param query is the hibernate query to attach to
1016    * @param params (null, Object, Object[], or List of Objects)
1017    * @param types (null, Type, Type[], or List of Types)
1018    */
1019   @SuppressWarnings("deprecation")
1020   static void attachParams(Query query, Object params, Object types) {
1021 
1022     //nothing to do if nothing to do
1023     if (GrouperUtil.length(params) == 0 && GrouperUtil.length(types) == 0) {
1024       return;
1025     }
1026 
1027     if (GrouperUtil.length(params) != GrouperUtil.length(types)) {
1028       throw new RuntimeException("The params length must equal the types length and params " +
1029       "and types must either both or neither be null");
1030     }
1031 
1032     int paramLength = -1;
1033 
1034 
1035     List paramList = GrouperUtil.toList(params);
1036     List typeList = GrouperUtil.toList(types);
1037 
1038 
1039       paramLength = paramList.size();
1040 
1041       if (paramLength != typeList.size()) {
1042         throw new RuntimeException("The params length " + paramLength
1043             + " must equal the types length " + typeList.size());
1044       }
1045 
1046       //loop through, set the params
1047       for (int i = 0; i < paramLength; i++) {
1048         //massage types
1049         Object param = paramList.get(i);
1050         Type type = (Type) typeList.get(i);
1051         
1052         //convert date
1053         if (type.equals(StandardBasicTypes.DATE)) {
1054           type = StandardBasicTypes.TIMESTAMP;
1055           //convert the data
1056           param = GrouperUtil.toTimestamp(param);
1057         }
1058         
1059         query.setParameter(i, param, type);
1060       }
1061 
1062 
1063     }
1064 
1065 }