View Javadoc
1   package edu.internet2.middleware.grouperClient.jdbc;
2   
3   
4   import java.lang.reflect.Constructor;
5   import java.lang.reflect.Field;
6   import java.lang.reflect.Modifier;
7   import java.math.BigDecimal;
8   import java.sql.CallableStatement;
9   import java.sql.Clob;
10  import java.sql.Connection;
11  import java.sql.DriverManager;
12  import java.sql.PreparedStatement;
13  import java.sql.ResultSet;
14  import java.sql.ResultSetMetaData;
15  import java.sql.SQLException;
16  import java.sql.Savepoint;
17  import java.sql.Types;
18  import java.text.SimpleDateFormat;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Date;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.LinkedHashMap;
25  import java.util.LinkedHashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.regex.Pattern;
30  
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  
35  import edu.internet2.middleware.grouperClient.collections.MultiKey;
36  import edu.internet2.middleware.grouperClient.util.GrouperClientConfig;
37  import edu.internet2.middleware.grouperClient.util.GrouperClientUtils;
38  import edu.internet2.middleware.morphString.Morph;
39  
40  
41  /**
42   * <p>Use this class to get access to the global database connections, create a new connection, 
43   * and execute sql against them.</p>
44   * <p>Sample call
45   * 
46   * <blockquote>
47   * <pre>
48   * Timestamp lastSuccess = new GcDbAccess().sql("select max(ended_time) from grouper_loader_log where job_name = ?")
49   *   .addBindVar("CHANGE_LOG_consumer_recentMemberships").select(Timestamp.class);
50   * </pre>
51   * </blockquote>
52   * 
53   * </p>
54   * From a database external system
55   * <blockquote>
56   * <pre>
57   * Integer theOne = new GcDbAccess().connectionName(externalSystemConfigId).sql("select 1 from dual")
58   *   .select(Integer.class);
59   * </pre>
60   * </blockquote>
61   * 
62   */
63  public class GcDbAccess {
64  
65    /**
66     * if grouper started
67     */
68    private static boolean grouperIsStarted = false;
69    
70    
71    /**
72     * if grouper started
73     * @return the grouperIsStarted
74     */
75    public static boolean isGrouperIsStarted() {
76      return grouperIsStarted;
77    }
78    
79    /**
80     * if grouper started
81     * @param theGrouperIsStarted the grouperIsStarted to set
82     */
83    public static void setGrouperIsStarted(boolean theGrouperIsStarted) {
84      GcDbAccess.grouperIsStarted = theGrouperIsStarted;
85    }
86  
87  
88    /**
89     * A map to cache result bean data in based on a key, and host it for a particular amount of time.
90     */
91    private static Map<MultiKey, GcDbQueryCache> dbQueryCacheMap = new GcDbQueryCacheMap();
92  
93  
94    /**
95     * How long the currently selected objects will be stored in the cache.
96     */
97    private Integer cacheMinutes;
98  
99    /**
100    * If true, the map queryAndTime will be populated with date regarding the time spent in each unique query (unique by query string, not considering bind variable values).
101    */
102   private static boolean accumulateQueryMillis;
103 
104   /**
105    * A map of the time spent in each unique query (unique by query string, not considering bind variable values).
106    */
107   private static Map<String, GcQueryReport> queriesAndMillis;
108 
109 
110   /**
111    * The amount of seconds that the query can run before being rolled back.
112    */
113   private Integer queryTimeoutSeconds;
114 
115   /**
116    * The list of bind variable objects.
117    */
118   private List<Object> bindVars;
119 
120   /**
121    * If you are executing a statement as a batch, this is the list of lists of bind variables to set.
122    */
123   private List<List<Object>> batchBindVars;
124 
125   /**
126    * If you are executing a statement as a batch, this is the batch size
127    */
128   private int batchSize = -1;
129   
130   /**
131    * Set to true if we're assuming inserts over updates without checking the database
132    */
133   private boolean isInsert = false;
134   
135   /**
136    * If we're doing a batch store and there's an exception, should we retry
137    */
138   private boolean retryBatchStoreFailures = false;
139   
140   /**
141    * If we're retrying a batch store and there are still failures, should we ignore it
142    */
143   private boolean ignoreRetriedBatchStoreFailures = false;
144 
145   /**
146    * batch size
147    * @param theBatchSize
148    * @return this for chaining
149    */
150   public GcDbAccess batchSize(int theBatchSize) {
151     this.batchSize = theBatchSize;
152     return this;
153   }
154   
155   /**
156    * If we're doing a batch store and there's an exception, should we retry
157    * @param retryBatchStoreFailures
158    * @return this for chaining
159    */
160   public GcDbAccess retryBatchStoreFailures(boolean retryBatchStoreFailures) {
161     this.retryBatchStoreFailures = retryBatchStoreFailures;
162     return this;
163   }
164   
165   public GcDbAccess ignoreRetriedBatchStoreFailures(boolean ignoreRetriedBatchStoreFailures) {
166     this.ignoreRetriedBatchStoreFailures = ignoreRetriedBatchStoreFailures;
167     return this;
168   }
169   
170   /**
171    * Set to true if we're assuming inserts over updates without checking the database
172    * @param isInsert
173    * @return this for chaining
174    */
175   public GcDbAccess isInsert(boolean isInsert) {
176     this.isInsert = isInsert;
177     return this;
178   }
179   
180   /**
181    * keep a query count in this thread
182    */
183   private static InheritableThreadLocal<Integer> queryCount = new InheritableThreadLocal<Integer>();
184 
185   /**
186    * reset the query count
187    */
188   public static void threadLocalQueryCountReset() {
189     queryCount.set(0);
190   }
191   
192   /**
193    * get the query count
194    * @return query count
195    */
196   public static int threadLocalQueryCountRetrieve() {
197     Integer queryCountInteger = queryCount.get();
198     return queryCountInteger == null ? 0 : queryCountInteger;
199   }
200 
201   /**
202    * 
203    * @param queriesToAdd
204    */
205   public synchronized static void threadLocalQueryCountIncrement(int queriesToAdd) {
206     Integer queryCountInteger = queryCount.get();
207     queryCount.set((queryCountInteger == null ? 0 : queryCountInteger) + queriesToAdd);
208   }
209 
210   /**
211    * The sql to execute.s
212    */
213   private String sql;
214   
215   /**
216    * if you want to select rows in a batch based on one column, this is the column name
217    * e.g.
218    * <pre> 
219    * List<String> deprovisioningStemAttributeAssignIds = new GcDbAccess().sql(
220    * "select attribute_assign_id2 from grouper_aval_asn_asn_stem_v gaaasv")
221    * .selectMultipleColumnName("attribute_assign_id2").addBindVars(attributeAssignIds).selectList(String.class);
222    * </pre>
223    */
224   private String selectMultipleColumnName;
225 
226   /**
227    * table name if not from annotation
228    */
229   private String tableName;
230   
231   /**
232    * table name if not from annotation
233    * @param theTableName
234    * @return the table name
235    */
236   public GcDbAccess tableName(String theTableName) {
237     this.tableName = theTableName;
238     return this;
239   }
240   
241   /**
242    * use the table passed in or from annotation
243    */
244   private String tableName(Class<?> theClass) {
245     if (!GrouperClientUtils.isBlank(this.tableName)) {
246       return this.tableName;
247     }
248     return GcPersistableHelper.tableName(theClass);
249   }
250   
251   /**
252    * If selecting something by primary key, this is one or many keys.
253    */
254   private List<Object> primaryKeys;
255 
256 
257   /**
258    * connection name from the config file, or null for default
259    */
260   private String connectionName;
261  
262   /**
263    * connection name from the config file, or null for default
264    * @param theConnectionName
265    * @return this for chaining
266    */
267   public GcDbAccess connectionName(String theConnectionName) {
268     this.connectionName = theConnectionName;
269     this.connectionProvided = false;
270     return this;
271   }
272   
273   /**
274    * end a transaction
275    * @param transactionEnd
276    * @param endOnlyIfStarted
277    */
278   public static void transactionEnd(GcTransactionEnd transactionEnd, boolean endOnlyIfStarted) {
279     transactionEnd(transactionEnd, endOnlyIfStarted, null, false);
280   }
281   
282   /**
283    * end a transaction
284    * @param transactionEnd
285    * @param endOnlyIfStarted
286    * @param connectionName 
287    * @param connectionProvided if connection is provided
288    */
289   public static void transactionEnd(GcTransactionEnd transactionEnd, boolean endOnlyIfStarted, String connectionName) {
290     transactionEnd(transactionEnd, endOnlyIfStarted, connectionName, false);
291   }
292   
293   /**
294    * end a transaction
295    * @param transactionEnd
296    * @param endOnlyIfStarted
297    * @param connectionName 
298    * @param connectionProvided if connection is provided
299    */
300   public static void transactionEnd(GcTransactionEnd transactionEnd, boolean endOnlyIfStarted, String connectionName, boolean connectionProvided) {
301     
302     if (connectionProvided) {
303       return;
304     }
305     ConnectionBean connectionBean = connection(false, false, connectionName, false, null);
306     
307     Connection connection = connectionBean.getConnection();
308     
309     if (connection == null) {
310       throw new RuntimeException("There is no connection!");
311     }
312 
313     ConnectionBean.transactionEnd(connectionBean, transactionEnd, endOnlyIfStarted, true, false);
314   }
315 
316   /**
317    * If the connection is provided externally
318    */
319   private boolean connectionProvided;
320 
321   /**
322    * The connection that we are using.
323    */
324   private Connection connection;
325 
326   /**
327    * If selecting by example, set this and all column values will be used to create a where clause.
328    */
329   private Object example;
330 
331 
332   /**
333    * If selecting by example, set this and all column values of the given example object except null values will be used to create a where clause.
334    */
335   private boolean omitNullValuesForExample;
336 
337 
338   /**
339    * The number of rows touched if an update, delete, etc was executed.
340    */
341   private int numberOfRowsAffected;
342 
343 
344   /**
345    * The number of batch rows touched if an update, delete, etc was executed.
346    */
347   private int numberOfBatchRowsAffected[];
348 
349 
350   /**
351    * <pre>This is our helper to convert data to and from Oracle. It is externalized because it will likely be 
352    * common that editing will need to be done on a per project basis.</pre>
353    */
354   private static GcBoundDataConversion boundDataConversion = new GcBoundDataConversionImpl();
355 
356 
357   /**
358    * Whether we registered all of the dbconnection classes yet or not.
359    */
360   private static boolean dbConnectionClassesRegistered = false;
361 
362   /**
363    * This is the helper to convert data to and from Oracle, which has a default of BoundDataConversionImpl. 
364    * If you encounter errors getting and setting data from oracle to java, you may need to override the default
365    * and set your version here. Otherwise, nothing is needed.
366    * @param _boundDataConversion the boundDataConversion to set.
367    */
368   public static void loadBoundDataConversion(GcBoundDataConversion _boundDataConversion) {
369     boundDataConversion = _boundDataConversion;
370   }
371 
372 
373 
374   /**
375    * Create an in statement with the given number of bind variables: createInString(2) returns " (?,?) "
376    * @param numberOfBindVariables is the number of bind variables to use.
377    * @return the string.
378    */
379   public static String createInString(int numberOfBindVariables){
380     StringBuilder results = new StringBuilder(" (");
381     for (int i=0; i<numberOfBindVariables; i++){
382       results.append("?,");
383     }
384     GrouperClientUtils.removeEnd(results, ",");
385     results.append(") ");
386     return results.toString();
387   }
388 
389 
390   
391   public List<Object> getBindVars() {
392     return bindVars;
393   }
394 
395   
396   /**
397     /**
398    * Set the list of bind variable objects, always replacing any that exist.
399    * @param _bindVars are the variables to add to the list.
400    * @return this.
401    */
402   public GcDbAccess bindVars(Object... _bindVars){
403     this.bindVars = new ArrayList<Object>();
404 
405     for (Object bindVar : _bindVars){
406       if (bindVar instanceof List){
407         List<?> arrayData = (List<?>)bindVar;
408         for (Object value : arrayData){
409           this.bindVars.add(value);
410         }             
411       } else {
412         this.bindVars.add(bindVar);
413       }   
414     }
415 
416     return this;
417   }
418 
419 
420 
421   /**
422    * Add to the list of bind variable objects, leaving any that exist there - if you use this in a transaction callback
423    * you will have to clear bindvars between calls or they will accumulate.
424    * @param _bindVar is the variable to add to the list.
425    * @return this.
426    */
427   public GcDbAccess addBindVar(Object _bindVar){
428 
429     if (this.bindVars == null){
430       this.bindVars = new ArrayList<Object>();
431     }
432 
433     this.bindVars.add(_bindVar);
434 
435     return this;
436   }
437 
438   /**
439    * Add to the list of bind variable objects, leaving any that exist there - if you use this in a transaction callback
440    * you will have to clear bindvars between calls or they will accumulate.
441    * @param _bindVar is the variable to add to the list.
442    * @return this.
443    */
444   public GcDbAccess addBindVars(Collection<?> _bindVar){
445 
446     if (this.bindVars == null){
447       this.bindVars = new ArrayList<Object>();
448     }
449 
450     this.bindVars.addAll(_bindVar);
451 
452     return this;
453   }
454 
455 
456   /**
457    * If you are executing sql as a batch statement, set the batch bind variables here.
458    * @param _batchBindVars are the variables to set.
459    * @return this.
460    */
461   public GcDbAccess batchBindVars(List<List<Object>> _batchBindVars){
462     this.batchBindVars = _batchBindVars;
463     return this;
464   }
465 
466 
467   /**
468    * <pre>Cache the results of a SELECT query for the allotted minutes.
469    * Note that cached objects are not immutable; if you modify them you are modifying them in the cache as well.</pre>
470    * @param _cacheMinutes is how long to persist the object(s) in cache for after the initial selection.
471    * @return this.
472    */
473   public GcDbAccess cacheMinutes(Integer _cacheMinutes){
474     this.cacheMinutes = _cacheMinutes;
475     return this;
476   }
477 
478 
479   /**
480    * Set the sql to use.
481    * @param _sql is the sql to use.
482    * @return this.
483    */
484   public GcDbAccess sql(String _sql){
485     this.sql = _sql;
486     return this;
487   }
488   
489   /**
490    * Set the selectMultipleColumnName to use.
491    * e.g. 
492    * <pre> 
493    * List<String> deprovisioningStemAttributeAssignIds = new GcDbAccess().sql(
494    * "select attribute_assign_id2 from grouper_aval_asn_asn_stem_v gaaasv")
495    * .selectMultipleColumnName("attribute_assign_id2").addBindVars(attributeAssignIds).selectList(String.class);
496    * </pre>
497    * @param selectMultipleColumnName is the sql column 
498    * @return this.
499    */
500   public GcDbAccess selectMultipleColumnName(String selectMultipleColumnName){
501     this.selectMultipleColumnName = selectMultipleColumnName;
502     return this;
503   }
504 
505 
506   /**
507    * If selecting by example, set this and all column values of the given example object except null values will be used to create a where clause.
508    * @return this.
509    */
510   public GcDbAccess omitNullValuesForExample(){
511     this.omitNullValuesForExample = true;
512     return this;
513   }
514 
515 
516 
517 
518   /**
519    * If selecting by example, set this and all column values will be used to create a where clause.
520    * @param _example is the example to use.
521    * @return this.
522    */
523   public GcDbAccess example(Object _example){
524     this.example = _example;
525     return this;
526   }
527 
528 
529   /**
530    * The amount of seconds that the query can run before being rolled back.
531    * @param _queryTimeoutSeconds is the amount of seconds to set.
532    * @return this.
533    */
534   public GcDbAccess queryTimeoutSeconds(Integer _queryTimeoutSeconds){
535     this.queryTimeoutSeconds = _queryTimeoutSeconds;
536     return this;
537   }
538 
539 
540 
541   /**
542    * <pre>If true, the map queryAndTime will be populated with the time spent in each unique query (unique by query string, not considering bind variable values) 
543    * - BE SURE TO TURN THIS OFF when done debugging, this is ONLY for debugging on the desktop!Turning it off CLEARS the stats, so write it off first!
544    * Example:
545    * 1. DbAccess.accumulateQueryMillis(true);
546    * 2. use application normally
547    * 3. Get the results: Map<String, Long> timeSpentInQueries = 
548    * </pre>
549    * @param _accumulateQueryMillis is whether to accumulate them or not.
550    */
551   public static void accumulateQueryMillis(boolean _accumulateQueryMillis){
552     if (_accumulateQueryMillis){
553       queriesAndMillis = new LinkedHashMap<String, GcQueryReport>();
554     } else {
555       queriesAndMillis.clear();
556     }
557     accumulateQueryMillis = _accumulateQueryMillis;
558   }
559 
560 
561   /**
562    * <pre>Write the stats of queries and time spent in them to a file at the given location, then stop collection stats. accumulateQueryMillis(true)
563    * must be called first to turn on debugging.</pre>
564    * @param fileLocation is the location of the file to write.
565    */
566   public static void reportQueriesAndMillisAndTurnOffAccumulation(String fileLocation){
567     if (!accumulateQueryMillis){
568       throw new RuntimeException("accumulateQueryMillis must be set to true first!");
569     }
570     GcQueryReport.reportToFile(fileLocation, queriesAndMillis);
571   }
572 
573   /**
574    * Set the primary key to select by.
575    * @param _primaryKey is the _primaryKey to use.
576    * @return this.
577    */
578   public GcDbAccess primaryKey(Object... _primaryKey){
579     this.primaryKeys = new ArrayList<Object>();
580 
581     if (_primaryKey != null && _primaryKey.length == 1 && _primaryKey[0] instanceof List){
582       List<?> arrayData = (List<?>)_primaryKey[0];
583       for (Object value : arrayData){
584         this.primaryKeys.add(value);
585       }
586     } else if (_primaryKey != null){
587       for (Object primaryKey : _primaryKey){
588         this.primaryKeys.add(primaryKey);
589       }
590     }
591     return this;
592   }
593 
594 
595   /**
596    * Add information about the query that was just executed.
597    * @param query is the query.
598    * @param nanoTimeStarted is how many millis were spent executing the query.
599    */
600   private void addQueryToQueriesAndMillis(String query, Long nanoTimeStarted){
601     if (!accumulateQueryMillis){
602       return;
603     }
604     GcQueryReport queryReport = queriesAndMillis.get(query);
605     if (queryReport == null){
606       queryReport = new GcQueryReport();
607       queryReport.setQuery(query);
608 
609       queriesAndMillis.put(query, queryReport);
610     }
611 
612     queryReport.addExecutionTime((System.nanoTime() - nanoTimeStarted) / 1000000);
613   }
614 
615   /**
616    * <pre>Whether classes (all of which must have the same type) have already been saved to the database, looks for a field(s) with annotation @Persistable(primaryKeyField=true),
617    * assumes that it is a number, and returns true if it is null or larger than 0.</pre>
618    *  @param objects is the object to store to the database.
619    * @return map of objects passed in to boolean if previously persisted
620    */
621   public Map<Object, Boolean> isPreviouslyPersisted(List<Object> objects) {
622     Map<Object, Boolean> results = new LinkedHashMap<>();
623     
624     if (objects.size() == 0) {
625       return results;
626     }
627 
628     Field field = GcPersistableHelper.primaryKeyField(objects.get(0).getClass());
629     List<Field> compoundPrimaryKeys =  GcPersistableHelper.compoundPrimaryKeyFields(objects.get(0).getClass());
630 
631     // Objects with no PK are never considered previously persisted.
632     if (field == null && compoundPrimaryKeys.size() == 0){
633       for (Object object : objects) {
634         results.put(object, false);
635       }
636       
637       return results;
638     }
639 
640     List<Object> objectsToQueryDatabase = new ArrayList<Object>();
641 
642     // We have a single primary key.
643     if (field != null) {
644       for (Object object: objects) {
645         Object fieldValue = null;
646         try {
647           fieldValue = field.get(object);
648         } catch (Exception e) {
649           throw new RuntimeException(e);
650         } 
651   
652         if (fieldValue == null){
653           results.put(object, false);
654         } else if (GcPersistableHelper.primaryKeyManuallyAssigned(field)){
655 
656           // If it was manually assigned, we have to check the database.
657           objectsToQueryDatabase.add(object);
658 
659         } else if (fieldValue != null && fieldValue instanceof String) {
660           // if field is string, it must be not null
661           results.put(object, true);
662         } else {
663           try {
664             // If field is numeric, it must be > 0.
665             Long theId = new Long(String.valueOf(fieldValue));
666             results.put(object, theId > 0);
667           } catch (Exception e){
668             throw new RuntimeException("Expected primary key field of numeric type but got " + field.getName() + " of type " + field.getClass() + ". You need to override isPreviouslyPersisted() or provide a Persistable annotation for your primary key!", e);
669           }
670         }
671       }
672     }
673 
674     // We have multiple primary keys.
675     if (compoundPrimaryKeys.size() > 0){
676       objectsToQueryDatabase.addAll(objects);
677     }
678     
679     if (objectsToQueryDatabase.size() > 0) {
680       
681       List<Field> fields = new ArrayList<>();
682       if (field != null) {
683         fields.add(field);
684       } else {
685         fields.addAll(compoundPrimaryKeys);
686       }
687       
688       List<String> fieldNames = new ArrayList<>();
689       for (Field currentField : fields) {
690         fieldNames.add(GcPersistableHelper.columnName(currentField));
691       }
692       
693       List<Object> theBindVariables = new ArrayList<Object>();
694 
695       String theSql = "select " + String.join(",", fieldNames) + " from " + this.tableName(objectsToQueryDatabase.get(0).getClass()) + " where ";
696 
697       Iterator<Object> objectsToQueryDatabaseIter = objectsToQueryDatabase.iterator();
698       while (objectsToQueryDatabaseIter.hasNext()) {
699         Object objectToQueryDatabase = objectsToQueryDatabaseIter.next();
700         
701         theSql += " ( ";
702         
703         Iterator<Field> fieldsIter = fields.iterator();
704         while (fieldsIter.hasNext()) {
705           Field currentField = fieldsIter.next();
706           Object fieldValue = null;
707           try {
708             fieldValue = currentField.get(objectToQueryDatabase);
709           } catch (Exception e) {
710             throw new RuntimeException(e);
711           } 
712           theSql += GcPersistableHelper.columnName(currentField) + " = ? ";
713           
714           if (fieldsIter.hasNext()) {
715             theSql += " and ";
716           }
717           
718           theBindVariables.add(fieldValue);
719         }
720         
721         theSql += " ) ";
722         
723         if (objectsToQueryDatabaseIter.hasNext()) {
724           theSql += " or ";
725         }
726       }
727 
728       Long startTime = System.nanoTime();
729 
730       List<Object[]> queryResults = new GcDbAccess()
731           .connection(this.connection)
732           .sql(theSql)
733           .bindVars(theBindVariables)
734           .selectList(Object[].class);
735 
736       this.addQueryToQueriesAndMillis(theSql, startTime);
737       
738       // create a set of keys that were found in the database
739       Set<MultiKey> foundKeys = new LinkedHashSet<MultiKey>();
740       
741       for (Object[] queryResult : queryResults) {
742         
743         Object[] key = new Object[fields.size()];
744         for (int i = 0; i < queryResult.length; i++) {
745           Object queryResultValue = queryResult[i];
746           Field currentField = fields.get(i);
747           
748           if (currentField.getType() == Long.class || currentField.getType() == long.class) {
749             Long value = ((Number)queryResultValue).longValue();
750             key[i] = value;
751           } else if (currentField.getType() == Integer.class || currentField.getType() == int.class) {
752             Integer value = ((Number)queryResultValue).intValue();
753             key[i] = value;
754           } else if (currentField.getType() == String.class) {
755             String value = (String)queryResultValue;
756             key[i] = value;
757           } else {
758             key[i] = queryResultValue;
759           }
760         }
761         
762         foundKeys.add(new MultiKey(key));
763       }
764       
765       // now check if the objects we queried were found or not
766       for (Object object : objectsToQueryDatabase) {
767         Object[] key = new Object[fields.size()];
768         for (int i = 0; i < fields.size(); i++) {
769           Field currentField = fields.get(i);
770           Object fieldValue = null;
771           try {
772             fieldValue = currentField.get(object);
773           } catch (Exception e) {
774             throw new RuntimeException(e);
775           } 
776 
777           key[i] = fieldValue;
778         }
779         
780         if (foundKeys.contains(new MultiKey(key))) {
781           results.put(object, true);
782         } else {
783           results.put(object, false);
784         }
785       }
786     }
787     
788     return results;
789   }
790   
791   
792   /**
793    * <pre>Whether this class has already been saved to the database, looks for a field(s) with annotation @Persistable(primaryKeyField=true),
794    * assumes that it is a number, and returns true if it is null or larger than 0.</pre>
795    *  @param o is the object to store to the database.
796    * @return true if so.
797    */
798   public boolean isPreviouslyPersisted(Object o){
799     List<Object> objects = new ArrayList<>();
800     objects.add(o);
801     Map<Object, Boolean> results = isPreviouslyPersisted(objects);
802     return results.get(o);
803   }
804 
805   public void deleteFromDatabaseMultiple(Collection<? extends Object> objects) {
806     Map<Class<?>, List<Object>> typeToObjects = new HashMap<>();
807     
808     for(Object o: GrouperClientUtils.nonNull(objects)) {     
809       List<Object> listOfObjects = typeToObjects.get(o.getClass());
810       if (listOfObjects == null) {
811         listOfObjects = new ArrayList<Object>();
812         typeToObjects.put(o.getClass(), listOfObjects);
813       }
814       listOfObjects.add(o);
815     }
816     
817     for (List<Object> listOfObjects: typeToObjects.values()) {
818       deleteFromDatabaseMultipleSameType(listOfObjects);
819     }
820     
821   }
822   
823   private void deleteFromDatabaseMultipleSameType(Collection<? extends Object> objects) {
824     
825     if (GrouperClientUtils.nonNull(objects).size() == 0) {
826       return;
827     }
828     
829     ArrayList<? extends Object> objectsToBeDeleted = new ArrayList<>(objects);
830     
831     String primaryKeyColumnName = null;
832     List<String> primaryKeyColumnNames = new ArrayList<String>();
833     String tableName = null;
834     List<List<Object>> primaryKeys = new ArrayList<>();
835     List<List<Object>> compoundPrimaryKeysList = new ArrayList<>();
836     
837     Field primaryKeyField = null;
838     List<Field> compoundPrimaryKeys = null;
839     
840     for (Object objectToBeDeleted: objectsToBeDeleted) {
841       
842       tableName = tableName != null ? tableName : this.tableName(objectToBeDeleted.getClass()); // it should be the same for every object in the list
843       
844       primaryKeyField = primaryKeyField != null ? primaryKeyField : GcPersistableHelper.primaryKeyField(objectToBeDeleted.getClass());
845       compoundPrimaryKeys = compoundPrimaryKeys != null ? compoundPrimaryKeys : GcPersistableHelper.compoundPrimaryKeyFields(objectToBeDeleted.getClass());
846       
847       if (primaryKeyField == null && compoundPrimaryKeys.size() == 0) {
848         throw new RuntimeException("Cannot delete a row with no primary key or compound primary keys - use sql to delete the row instead of the method deleteFromDatabase().");
849       }
850       
851       if (primaryKeyField != null) {
852         primaryKeyColumnName = primaryKeyColumnName != null ? primaryKeyColumnName : GcPersistableHelper.columnName(primaryKeyField); // it should be the same for every object in the list
853         Object primaryKey = null;
854 
855         try {
856           primaryKey = primaryKeyField.get(objectToBeDeleted);
857         } catch (Exception e) {
858           throw new RuntimeException(e);
859         } 
860 
861         List<Object> primaryKeyList = new ArrayList<Object>();
862         primaryKeyList.add(primaryKey);
863         primaryKeys.add(primaryKeyList);
864         
865         
866       } else {
867         
868         if (primaryKeyColumnNames.size() == 0) {
869           for (Field compoundPrimaryKey : compoundPrimaryKeys){
870             primaryKeyColumnNames.add(GcPersistableHelper.columnName(compoundPrimaryKey));
871           }
872         }
873         
874         List<Object> primaryKeyValues = new ArrayList<Object>();
875 
876         try {
877           for (Field compoundPrimaryKey : compoundPrimaryKeys){
878             Object primaryKeyValue = compoundPrimaryKey.get(objectToBeDeleted);
879             primaryKeyValues.add(primaryKeyValue);
880           }
881           
882         } catch (Exception e) {
883           throw new RuntimeException(e);
884         } 
885         
886         compoundPrimaryKeysList.add(primaryKeyValues);
887 
888       }
889 
890     }
891     
892     if (primaryKeys.size() > 0) {
893       
894       String sqlToUse = "delete from " + tableName + " where " + primaryKeyColumnName + " =  ? ";
895 
896       this.sql(sqlToUse);
897       this.batchBindVars(primaryKeys);
898       this.executeBatchSql();
899 
900       for (Object objectToBeDeleted: objectsToBeDeleted) {
901         if (objectToBeDeleted instanceof GcDbVersionable) {
902           ((GcDbVersionable)objectToBeDeleted).dbVersionDelete();
903         }
904       }
905     } else if (compoundPrimaryKeysList.size() > 0) {
906       
907       StringBuilder sqlToUse = new StringBuilder("delete from " + tableName + " where ");
908       
909       boolean isFirst = true;
910       
911       for (String primaryKeyColumn: primaryKeyColumnNames) {
912         if (!isFirst) {
913           sqlToUse.append(" and ");
914         }
915         sqlToUse.append(primaryKeyColumn).append(" = ? ");
916         isFirst = false;
917       }
918       
919       this.sql(sqlToUse.toString());
920       this.batchBindVars(compoundPrimaryKeysList);
921       this.executeBatchSql();
922 
923       for (Object objectToBeDeleted: objectsToBeDeleted) {
924         if (objectToBeDeleted instanceof GcDbVersionable) {
925           ((GcDbVersionable)objectToBeDeleted).dbVersionDelete();
926         }
927       }
928       
929     }
930     
931     
932   }
933   
934 
935   /**
936    * Delete the object from  the database if it has already been stored - the object should have appropriate annotations from the PersistableX annotations.
937    * @see GcPersistableClass - this annotation must be placed at the class level.
938    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
939    *  @param o is the object to delete from the database.
940    */
941   public  void deleteFromDatabase(Object o){
942     if (!isPreviouslyPersisted(o)){
943       if (!GcPersistableHelper.defaultUpdate(o.getClass())) {
944         return;
945       }
946     }
947 
948     Field primaryKeyField = GcPersistableHelper.primaryKeyField(o.getClass());
949 
950     List<Field> compoundPrimaryKeys =  GcPersistableHelper.compoundPrimaryKeyFields(o.getClass());
951 
952 
953 
954     if (primaryKeyField == null && compoundPrimaryKeys.size() == 0){
955       throw new RuntimeException("Cannot delete a row with no primary key or compound primary keys - use sql to delete the row instead of the method deleteFromDatabase().");
956     }
957 
958 
959     // Single primary key.
960     if (primaryKeyField != null){
961       String primaryKeyColumnName = GcPersistableHelper.columnName(primaryKeyField);
962 
963       Object primaryKey = null;
964 
965       try {
966         primaryKey = primaryKeyField.get(o);
967       } catch (Exception e) {
968         throw new RuntimeException(e);
969       } 
970 
971       String tableName = this.tableName(o.getClass());
972 
973       String sqlToUse = "delete from " + tableName + " where " + primaryKeyColumnName + " = ? ";
974 
975       this.sql(sqlToUse);
976       this.bindVars(primaryKey);
977       this.executeSql();
978 
979       if (o instanceof GcDbVersionable) {
980         ((GcDbVersionable)o).dbVersionDelete();
981       }
982 
983       return;
984     }
985 
986 
987 
988     // Multiple column primary key.
989     if (compoundPrimaryKeys.size() > 0){
990       List<Object> theBindVariables = new ArrayList<Object>();
991 
992       String theSql = "delete from " + this.tableName(o.getClass()) + " where ";
993 
994       // Get all of the fields that are involved in the compound keys and build the sql and get bind variables.
995       for (Field compoundPrimaryKey : compoundPrimaryKeys){
996         Object fieldValue = null;
997         try {
998           fieldValue = compoundPrimaryKey.get(o);
999         } catch (Exception e) {
1000           throw new RuntimeException(e);
1001         } 
1002         theSql += GcPersistableHelper.columnName(compoundPrimaryKey) + " = ? and ";
1003         theBindVariables.add(fieldValue);
1004       }
1005       theSql = theSql.substring(0, theSql.length() - 4);
1006 
1007       this.sql(theSql);
1008       this.bindVars(theBindVariables);
1009       this.executeSql();
1010     }
1011 
1012     if (o instanceof GcDbVersionable) {
1013       ((GcDbVersionable)o).dbVersionDelete();
1014     }
1015   }
1016 
1017 
1018   /**
1019    * Store the given objects to the database in one transaction - the object should have appropriate annotations from the PersistableX annotations.
1020    * @see GcPersistableClass - this annotation must be placed at the class level.
1021    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
1022    * @param <T> is the type to store.
1023    * @param objects are the object to store to the database.
1024    */
1025   public <T> void storeListToDatabase(final List<T> objects){
1026     storeBatchToDatabase(objects, 200);
1027   }
1028 
1029 
1030   /**
1031    * Store the given object to the database - the object should have appropriate annotations from the PersistableX annotations.
1032    * @see GcPersistableClass - this annotation must be placed at the class level.
1033    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
1034    * @param <T> is the type to store.
1035    * @param t is the object to store to the database.
1036    * @return true if stored and false if not
1037    */
1038   public <T> boolean storeToDatabase(T t){
1039 
1040     if (t instanceof GcDbVersionable) {
1041       
1042       // dont store
1043       if (!((GcDbVersionable)t).dbVersionDifferent()) {
1044         return false;
1045       }
1046 
1047     }
1048 
1049     if (!GrouperClientUtils.isBlank(this.sql)){
1050       throw new RuntimeException("Cannot use both sql and set an object to store.");
1051     }
1052 
1053     Map<String, Object> columnNamesAndValues = new HashMap<String, Object>();
1054 
1055     // Get the primary key.
1056     Field primaryKey = GcPersistableHelper.primaryKeyField(t.getClass());
1057 
1058     boolean defaultUpdate = GcPersistableHelper.defaultUpdate(t.getClass());
1059 
1060     boolean previouslyPersisted = false;
1061     
1062     try{
1063       
1064       if (defaultUpdate) {
1065         previouslyPersisted = true;
1066       } else {
1067         previouslyPersisted = isPreviouslyPersisted(t);
1068       }
1069       
1070       boolean keepPrimaryKeyColumns = t instanceof GcSqlAssignPrimaryKey && !previouslyPersisted;
1071       
1072       if (keepPrimaryKeyColumns) {
1073         ((GcSqlAssignPrimaryKey)t).gcSqlAssignNewPrimaryKeyForInsert();
1074       }
1075       
1076       // Get column names and values
1077       for (Field field: GcPersistableHelper.heirarchicalFields(t.getClass())){
1078         field.setAccessible(true);
1079         // We are putting everything in here except the primary key because we may have to go out and get a primary key shortly
1080         // unless the primary key is manually assigned, in which case it can go in here.
1081         if ((primaryKey == null && GcPersistableHelper.isPersist(field, t.getClass())) || ( GcPersistableHelper.isPersist(field, t.getClass()) && (keepPrimaryKeyColumns || GcPersistableHelper.primaryKeyManuallyAssigned(primaryKey) || !GcPersistableHelper.isPrimaryKey(field)))){
1082           columnNamesAndValues.put(GcPersistableHelper.columnName(field), field.get(t));
1083         }
1084       }
1085 
1086       // Update if we are already saved.
1087 
1088       if (defaultUpdate) {
1089         try {
1090           int records = this.storeToDatabaseUpdateHelper(t, columnNamesAndValues, primaryKey);
1091           if (records == 0) {
1092             throw new RuntimeException("");
1093           }
1094         } catch (RuntimeException re) {
1095           if (LOG.isDebugEnabled()) {
1096             LOG.debug("Error trying to update a defaultUpdate record: " + t, re);
1097           }
1098           this.storeToDatabaseInsertHelper(t, columnNamesAndValues, primaryKey, keepPrimaryKeyColumns);
1099         }
1100       } else {
1101         if (previouslyPersisted){
1102           this.storeToDatabaseUpdateHelper(t, columnNamesAndValues, primaryKey);
1103         } else {
1104           this.storeToDatabaseInsertHelper(t, columnNamesAndValues, primaryKey, keepPrimaryKeyColumns);
1105         }
1106       }
1107       
1108       if (t instanceof GcDbVersionable) {
1109         ((GcDbVersionable)t).dbVersionReset();
1110       }
1111       return true;
1112     } catch (Exception e){
1113       throw new RuntimeException(e);
1114     }
1115   }
1116 
1117   /**
1118    * Insert into the database
1119    * @see GcPersistableClass - this annotation must be placed at the class level.
1120    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
1121    * @param <T> is the type to store.
1122    * @param t is the object to store to the database.
1123    */
1124   private <T> void storeToDatabaseInsertHelper(T t, Map<String, Object> columnNamesAndValues, Field primaryKey, boolean keepPrimaryKeyColumns){
1125 
1126     Object primaryKeyValue = null;
1127     List<Object> bindVarstoUse = new ArrayList<Object>();
1128     StringBuilder sqlToUse = new StringBuilder();
1129     try{
1130       
1131       // Else insert.
1132       sqlToUse.append(" insert into ").append(this.tableName(t.getClass())).append(" ( ");
1133       StringBuilder bindVarString = new StringBuilder("values (");
1134       for (String columnName : columnNamesAndValues.keySet()){
1135         sqlToUse.append(columnName + ",");
1136         bindVarString.append("?,");
1137         bindVarstoUse.add(columnNamesAndValues.get(columnName));
1138       }
1139 
1140       // Get a primary key from the sequence if it is not manually assigned.
1141       if (!keepPrimaryKeyColumns && primaryKey != null && !GcPersistableHelper.primaryKeyManuallyAssigned(primaryKey) && !GcPersistableHelper.findPersistableClassAnnotation(t.getClass()).hasNoPrimaryKey()){
1142 
1143         sqlToUse.append(GcPersistableHelper.columnName(primaryKey));
1144         sqlToUse.append(") ");
1145 
1146         primaryKeyValue = new GcDbAccess()
1147             .connection(this.connection)
1148             .sql(" select " + GcPersistableHelper.primaryKeySequenceName(primaryKey) + ".nextval from dual")
1149             .select(primaryKey.getType());
1150 
1151         bindVarstoUse.add(primaryKeyValue);
1152         bindVarString.append("?) ");
1153 
1154       } else {
1155         sqlToUse = new StringBuilder(GrouperClientUtils.removeEnd(sqlToUse.toString(), ",") + ") ");
1156         bindVarString = new StringBuilder(GrouperClientUtils.removeEnd(bindVarString.toString(), ",") + ") ");
1157       }
1158 
1159 
1160       sqlToUse.append(bindVarString);
1161 
1162       // Execute the insert or update.
1163       this.sql(sqlToUse.toString());
1164       this.bindVars(bindVarstoUse);
1165       this.executeSql();
1166 
1167       // Set the primary key if it was an insert and we grabbed a new one.
1168       if (primaryKeyValue != null){
1169         boundDataConversion.setFieldValue(t, primaryKey, primaryKeyValue);
1170       }
1171 
1172     } catch (Exception e){
1173       throw new RuntimeException(e);
1174     }
1175   }
1176 
1177   /**
1178    * Update the database
1179    * @see GcPersistableClass - this annotation must be placed at the class level.
1180    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
1181    * @param <T> is the type to store.
1182    * @param t is the object to store to the database.
1183    */
1184   private <T> int storeToDatabaseUpdateHelper(T t, Map<String, Object> columnNamesAndValues, Field primaryKey){
1185 
1186     List<Object> bindVarstoUse = new ArrayList<Object>();
1187 
1188     List<Field> compoundPrimaryKeys =  GcPersistableHelper.compoundPrimaryKeyFields(t.getClass());
1189 
1190     try{
1191       
1192       boolean previouslyPersisted = isPreviouslyPersisted(t);
1193 
1194       boolean keepPrimaryKeyColumns = t instanceof GcSqlAssignPrimaryKey && !previouslyPersisted;
1195       
1196       if (keepPrimaryKeyColumns) {
1197         ((GcSqlAssignPrimaryKey)t).gcSqlAssignNewPrimaryKeyForInsert();
1198       }
1199       
1200       // Get column names and values
1201       for (Field field: GcPersistableHelper.heirarchicalFields(t.getClass())){
1202         field.setAccessible(true);
1203         // We are putting everything in here except the primary key because we may have to go out and get a primary key shortly
1204         // unless the primary key is manually assigned, in which case it can go in here.
1205         if ((primaryKey == null && GcPersistableHelper.isPersist(field, t.getClass())) || ( GcPersistableHelper.isPersist(field, t.getClass()) && (keepPrimaryKeyColumns || GcPersistableHelper.primaryKeyManuallyAssigned(primaryKey) || !GcPersistableHelper.isPrimaryKey(field)))){
1206           columnNamesAndValues.put(GcPersistableHelper.columnName(field), field.get(t));
1207         }
1208       }
1209 
1210       StringBuilder sqlToUse = new StringBuilder();
1211 
1212       // Update if we are already saved.
1213       sqlToUse.append(" update ").append(this.tableName(t.getClass())).append(" set ");
1214       for (String columnName : columnNamesAndValues.keySet()){
1215         sqlToUse.append(" ").append(columnName).append(" = ?, ");
1216         bindVarstoUse.add(columnNamesAndValues.get(columnName));
1217       }
1218       sqlToUse = new StringBuilder(GrouperClientUtils.removeEnd(sqlToUse.toString(), ", "));
1219 
1220 
1221       if (primaryKey != null){
1222         sqlToUse.append(" where ").append(GcPersistableHelper.columnName(primaryKey)).append(" = ? ");
1223         bindVarstoUse.add(primaryKey.get(t));
1224       } else if (compoundPrimaryKeys.size() > 0){
1225 
1226         sqlToUse.append(" where ");
1227 
1228         // Get all of the fields that are involved in the compound keys and build the sql and get bind variables.
1229         for (Field compoundPrimaryKey : compoundPrimaryKeys){
1230           Object fieldValue = null;
1231           try {
1232             fieldValue = compoundPrimaryKey.get(t);
1233           } catch (Exception e) {
1234             throw new RuntimeException(e);
1235           } 
1236           sqlToUse.append(GcPersistableHelper.columnName(compoundPrimaryKey)).append(" = ? and ");
1237           bindVarstoUse.add(fieldValue);
1238         }
1239         sqlToUse = new StringBuilder( sqlToUse.substring(0, sqlToUse.length() - 4));
1240       }
1241 
1242       // Execute the insert or update.
1243       this.sql(sqlToUse.toString());
1244       this.bindVars(bindVarstoUse);
1245       return this.executeSql();
1246 
1247     } catch (Exception e){
1248       throw new RuntimeException(e);
1249     }
1250   }
1251 
1252 
1253   
1254   /**
1255    * <pre>Store the given objects to the database in a batch - 
1256    * the objects should have appropriate annotations from the PersistableX annotations.
1257    * You cannot have both inserts and updates in the list of objects to store; they MUST all have the 
1258    * same action (insert or update) being taken against them as jdbc statements supoprt mutliple
1259    * sqls in a batch but do not support bind variables when using this capability.</pre>
1260    * @param <T> is the type to store.
1261    * @see GcPersistableClass - this annotation must be placed at the class level.
1262    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
1263    * @param objects is the list of objects to store to the database.
1264    * @param batchSize is the size of the batch to insert or update in.
1265    * @return number of changes
1266    */
1267   public <T> int storeBatchToDatabase(final List<T> objects, final int batchSize){
1268     return storeBatchToDatabase(objects, batchSize, false);
1269   }
1270 
1271   /**
1272    * <pre>Store the given objects to the database in a batch - 
1273    * the objects should have appropriate annotations from the PersistableX annotations.
1274    * You cannot have both inserts and updates in the list of objects to store; they MUST all have the 
1275    * same action (insert or update) being taken against them as jdbc statements supoprt mutliple
1276    * sqls in a batch but do not support bind variables when using this capability.</pre>
1277    * @param <T> is the type to store.
1278    * @see GcPersistableClass - this annotation must be placed at the class level.
1279    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
1280    * @param objects is the list of objects to store to the database.
1281    * @param batchSize is the size of the batch to insert or update in. e.g. GrouperClientConfig.retrieveConfig().propertyValueInt("grouperClient.syncTableDefault.maxBindVarsInSelect", 900)
1282    * @return number of changes
1283    */
1284   public <T> int storeBatchToDatabase(final Collection<T> objects, final int batchSize){
1285     if (GrouperClientUtils.length(objects) == 0) {
1286       return 0;
1287     }
1288     
1289     if (objects instanceof List) {
1290       return storeBatchToDatabase((List<T>)objects, batchSize);
1291     }
1292     
1293     return storeBatchToDatabase(new ArrayList<>(objects), batchSize, false);
1294   }
1295 
1296   /**
1297    * <pre>Store the given objects to the database in a batch - 
1298    * the objects should have appropriate annotations from the PersistableX annotations.
1299    * You cannot have both inserts and updates in the list of objects to store; they MUST all have the 
1300    * same action (insert or update) being taken against them as jdbc statements supoprt mutliple
1301    * sqls in a batch but do not support bind variables when using this capability.</pre>
1302    * @see GcPersistableClass - this annotation must be placed at the class level.
1303    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
1304    * @param <T> is the type being stored.
1305    * @param objects is the list of objects to store to the database.
1306    * @param batchSize is the size of the batch to insert or update in.
1307    * @param omitPrimaryKeyPopulation if you DON'T need primary keys populated into your objects, you can set this and save some query time since
1308    * we will just set the primary key population as "some_sequence.nextval" instead of selecting it manually before storing the object.
1309    * @return number of objects changed
1310    */
1311   public <T> int storeBatchToDatabase(List<T> objects, final int batchSize, final boolean omitPrimaryKeyPopulation){
1312 
1313     if (objects == null || objects.size() == 0){
1314       return 0;
1315     }
1316 
1317     // if we are checking db version, check that
1318     if (GrouperClientUtils.length(objects) > 0) {
1319       List<T> newList = new ArrayList<T>();
1320       for (T t : objects) {
1321         if (t instanceof GcDbVersionable) {
1322           if (((GcDbVersionable)t).dbVersionDifferent()) {
1323             newList.add(t);
1324           }
1325         } else {
1326           newList.add(t);
1327         }
1328       }
1329       objects = newList;
1330     }
1331 
1332     if (objects == null || objects.size() == 0){
1333       return 0;
1334     }
1335 
1336     if (objects.iterator().next() instanceof GcSqlAssignPrimaryKey) {
1337       
1338       List<T> objectsToInsert = new ArrayList<T>();
1339       List<T> objectsToUpdate = new ArrayList<T>();
1340       
1341       int changes = 0;
1342       
1343       Field primaryKey = GcPersistableHelper.primaryKeyField(objects.get(0).getClass());
1344       for (T t : objects) {
1345         Object primaryKeyValue = GrouperClientUtils.fieldValue(primaryKey, t);
1346         boolean isInsert = primaryKeyValue == null || (primaryKeyValue instanceof Long && ((Long)primaryKeyValue).longValue() == -1L);
1347         if (isInsert) {
1348           objectsToInsert.add(t);
1349         } else {
1350           objectsToUpdate.add(t);
1351         }
1352       }
1353       if (objectsToInsert.size() > 0) {
1354         changes += this.storeBatchToDatabaseHelper(objectsToInsert, batchSize, omitPrimaryKeyPopulation);
1355       }
1356       if (objectsToUpdate.size() > 0) {
1357         changes += this.storeBatchToDatabaseHelper(objectsToUpdate, batchSize, omitPrimaryKeyPopulation);
1358       }
1359       return changes;
1360     }
1361     return this.storeBatchToDatabaseHelper(objects, batchSize, omitPrimaryKeyPopulation);
1362     
1363   }
1364 
1365   /**
1366    * <pre>Store the given objects to the database in a batch - 
1367    * the objects should have appropriate annotations from the PersistableX annotations.
1368    * You cannot have both inserts and updates in the list of objects to store; they MUST all have the 
1369    * same action (insert or update) being taken against them as jdbc statements supoprt mutliple
1370    * sqls in a batch but do not support bind variables when using this capability.</pre>
1371    * @see GcPersistableClass - this annotation must be placed at the class level.
1372    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
1373    * @param <T> is the type being stored.
1374    * @param objects is the list of objects to store to the database.
1375    * @param batchSize is the size of the batch to insert or update in.
1376    * @param omitPrimaryKeyPopulation if you DON'T need primary keys populated into your objects, you can set this and save some query time since
1377    * we will just set the primary key population as "some_sequence.nextval" instead of selecting it manually before storing the object.
1378    * @return number of objects changed
1379    */
1380   private <T> int storeBatchToDatabaseHelper(List<T> objects, final int batchSize, final boolean omitPrimaryKeyPopulation){
1381 
1382     final List<T> OBJECTS = objects;
1383   
1384     final List<T> objectsToStore = new ArrayList<T>();
1385     final List<T> objectsToReturn = new ArrayList<T>();
1386   
1387     this.callbackTransaction(new GcTransactionCallback<Boolean>() {
1388   
1389       @Override
1390       public Boolean callback(GcDbAccess dbAccessForStorage) {
1391   
1392         for (int i=0; i < OBJECTS.size(); i++){
1393           
1394           // Add it.
1395           objectsToStore.add(OBJECTS.get(i));
1396   
1397           // If we have one batch or are at the end, store it.
1398           if (objectsToStore.size() >= batchSize || i == OBJECTS.size() -1){
1399             dbAccessForStorage.storeBatchToDatabase(objectsToStore, omitPrimaryKeyPopulation);
1400             objectsToReturn.addAll(objectsToStore);
1401             objectsToStore.clear();
1402           }
1403         }
1404   
1405         return null;
1406       }
1407     });
1408   
1409     int existingLength = objects.size();
1410     objects.clear();
1411     objects.addAll(objectsToReturn);
1412     if (objects.size() != existingLength){
1413       throw new RuntimeException("There should have been " + existingLength + " objects returned but there are only " + objects.size() + "!");
1414     }
1415     return objects.size();
1416   }
1417 
1418   /**
1419    * <pre>Store the given objects to the database in a batch - 
1420    * the objects should have appropriate annotations from the PersistableX annotations.
1421    * You cannot have both inserts and updates in the list of objects to store; they MUST all have the 
1422    * same action (insert or update) being taken against them as jdbc statements supoprt mutliple
1423    * sqls in a batch but do not support bind variables when using this capability.</pre>
1424    * @param <T> is the type being stored.
1425    * @see GcPersistableClass - this annotation must be placed at the class level.
1426    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
1427    * @param objects is the list of objects to store to the database.
1428    */
1429   private <T> void storeBatchToDatabase(List<T> objects){
1430     storeBatchToDatabase(objects, false);
1431   }
1432 
1433   /**
1434    * <pre>Store the given objects to the database in a batch - 
1435    * the objects should have appropriate annotations from the PersistableX annotations.
1436    * You cannot have both inserts and updates in the list of objects to store; they MUST all have the 
1437    * same action (insert or update) being taken against them as jdbc statements supoprt mutliple
1438    * sqls in a batch but do not support bind variables when using this capability.</pre>
1439    * @param <T> is the type being stored.
1440    * @see GcPersistableClass - this annotation must be placed at the class level.
1441    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
1442    * @param objects is the list of objects to store to the database.
1443    * @param omitPrimaryKeyPopulation if you DON'T need primary keys populated into your objects, you can set this and save some query time since
1444    * we will just set the primary key population as "some_sequence.nextval" instead of selecting it manually before storing the object.
1445    */
1446   private <T> void storeBatchToDatabase(List<T> objects, boolean omitPrimaryKeyPopulation){
1447     
1448     if (objects == null || objects.size() == 0) {
1449       return;
1450     }
1451     
1452     if ((GrouperClientUtils.length(objects) > 0) && GcPersistableHelper.defaultUpdate(objects.get(0).getClass())) {
1453       RuntimeException runtimeException = null;
1454       try {
1455         
1456         storeBatchToDatabaseHelper(objects, omitPrimaryKeyPopulation);
1457         
1458       } catch (RuntimeException re) {
1459         for (T object : objects) {
1460           try {
1461             storeToDatabase(object);
1462           } catch (RuntimeException re2 ) {
1463             LOG.error("error storing objects", re2);
1464             runtimeException = re2;
1465           }
1466         }
1467         if (runtimeException != null) {
1468           throw runtimeException;
1469         }
1470       }
1471       
1472     } else {
1473       Savepoint savepoint = null;
1474       if (this.retryBatchStoreFailures) {
1475         // set savepoint in case we get an error and need to retry
1476         try {
1477           savepoint = this.connection.setSavepoint();
1478         } catch (SQLException e) {
1479           throw new RuntimeException("Failed to set savepoint", e);
1480         }
1481       }
1482       
1483       try {
1484         storeBatchToDatabaseHelper(objects, omitPrimaryKeyPopulation);
1485       } catch (RuntimeException re) {
1486         if (!this.retryBatchStoreFailures) {
1487           throw re;
1488         }
1489         
1490         if (objects.size() == 1) {
1491           if (this.ignoreRetriedBatchStoreFailures) {
1492             LOG.warn("Failed to store object: " + objects.get(0), re);
1493             
1494             try {
1495               this.connection.rollback(savepoint);
1496             } catch (SQLException e) {
1497               throw new RuntimeException("Failed to rollback to savepoint", e);
1498             }
1499             
1500             return;
1501           } else {
1502             throw re;
1503           }
1504         }
1505         
1506         int newBatchSize = 50;
1507         if (objects.size() <= 50) {
1508           newBatchSize = 1;
1509         }
1510         
1511         try {
1512           this.connection.rollback(savepoint);
1513         } catch (SQLException e) {
1514           throw new RuntimeException("Failed to rollback to savepoint", e);
1515         }
1516         
1517         int numberOfBatches = GrouperClientUtils.batchNumberOfBatches(objects, newBatchSize, true);
1518         for (int batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) {
1519           this.sql(null);
1520           this.batchBindVars(null);
1521           
1522           List<?> objectsNewBatch = GrouperClientUtils.batchList(objects, newBatchSize, batchIndex);
1523           storeBatchToDatabase(objectsNewBatch, omitPrimaryKeyPopulation);
1524         }
1525       }
1526     }
1527     
1528   }
1529   
1530   /**
1531    * <pre>Store the given objects to the database in a batch - 
1532    * the objects should have appropriate annotations from the PersistableX annotations.
1533    * You cannot have both inserts and updates in the list of objects to store; they MUST all have the 
1534    * same action (insert or update) being taken against them as jdbc statements supoprt mutliple
1535    * sqls in a batch but do not support bind variables when using this capability.</pre>
1536    * @param <T> is the type being stored.
1537    * @see GcPersistableClass - this annotation must be placed at the class level.
1538    * @see GcPersistableField these annotations may be placed at the method level depending on your needs.
1539    * @param objects is the list of objects to store to the database.
1540    * @param omitPrimaryKeyPopulation if you DON'T need primary keys populated into your objects, you can set this and save some query time since
1541    * we will just set the primary key population as "some_sequence.nextval" instead of selecting it manually before storing the object.
1542    */
1543   private <T> void storeBatchToDatabaseHelper(List<T> objects, boolean omitPrimaryKeyPopulation){
1544 
1545     // No data nothing to do.
1546     if (objects == null || objects.size() == 0){
1547       return;
1548     }
1549 
1550     // We only want to formulate insert or update sql one time.
1551     String insertSql = null;
1552     String updateSql = null;
1553     boolean updateSqlInitialized = false;
1554     boolean insertSqlInitialized = false;
1555 
1556     if (!GrouperClientUtils.isBlank(this.sql)){
1557       throw new RuntimeException("Cannot use both sql and set objects to store.");
1558     }
1559 
1560     // Get the primary key or primary keys.
1561     Field primaryKey = GcPersistableHelper.primaryKeyField(objects.get(0).getClass());
1562 
1563     // Get any compound primary keys
1564     List<Field> compoundPrimaryKeys =  GcPersistableHelper.compoundPrimaryKeyFields(objects.get(0).getClass());
1565 
1566     // Get a list of all fields in the class.
1567     List<Field> allFields = GcPersistableHelper.heirarchicalFields(objects.get(0).getClass());
1568     
1569     // Create a map indicating which fields are to be included as bind variables.
1570     Map<Field, Boolean> fieldAndIncludeStatuses = new HashMap<Field, Boolean>();
1571     
1572     // Get field and the status of inclusion.
1573     for (Field field : allFields){
1574       field.setAccessible(true);
1575       // We are putting everything in here except the primary key because we may have to go out and get a primary key shortly
1576       // unless the primary key is manually assigned, in which case it can go in here.
1577       boolean persistField = GcPersistableHelper.isPersist(field, objects.get(0).getClass());
1578       boolean primaryKeyNullAndPersistField = primaryKey == null && persistField;
1579       boolean primaryKeyManuallyAssignedOrNotPrimaryKey = objects.get(0) instanceof GcSqlAssignPrimaryKey 
1580           || !GcPersistableHelper.isPrimaryKey(field) || GcPersistableHelper.primaryKeyManuallyAssigned(primaryKey);
1581       if (primaryKeyNullAndPersistField || ( persistField && primaryKeyManuallyAssignedOrNotPrimaryKey)){
1582         fieldAndIncludeStatuses.put(field, true);
1583       } else {
1584         fieldAndIncludeStatuses.put(field, false);
1585       }
1586     }
1587     
1588     try{
1589 
1590 
1591       // List of lists of bind variables.
1592       List<List<Object>> listsOfBindVars = new ArrayList<List<Object>>();
1593 
1594       // Store the primary keys back to the object after we save successfully.
1595       Map<Integer, Object> indexOfObjectAndPrimaryKeyToSet = new HashMap<Integer, Object>();
1596       
1597       List<Object> objectsToCheckIfPreviouslyPersisted = new ArrayList<>();
1598       for (Object object : objects){
1599         if (!(object instanceof GcSqlAssignPrimaryKey) && !this.isInsert) {
1600           objectsToCheckIfPreviouslyPersisted.add(object);
1601         }
1602       }
1603       
1604       Map<Object, Boolean> isPreviouslyPersisted = isPreviouslyPersisted(objectsToCheckIfPreviouslyPersisted);
1605       
1606       int objectIndex = 0;
1607 
1608       for (Object object : objects){
1609         
1610         Map<String, Object> columnNamesAndValues = new HashMap<String, Object>();
1611 
1612         boolean gcSqlAssignPrimaryKey = false;
1613         
1614         Boolean isInsert = null;
1615         
1616         if (object instanceof GcSqlAssignPrimaryKey) {
1617           Object primaryKeyValue = primaryKey.get(object);
1618           isInsert = primaryKeyValue == null || (primaryKeyValue instanceof Long && ((Long)primaryKeyValue).longValue() == -1L);
1619           if (isInsert) {
1620             ((GcSqlAssignPrimaryKey)object).gcSqlAssignNewPrimaryKeyForInsert();
1621             gcSqlAssignPrimaryKey = true;
1622           }
1623         }
1624         
1625         // Get column names and values
1626         for (Field field : allFields){
1627           if (fieldAndIncludeStatuses.get(field)){
1628             columnNamesAndValues.put(GcPersistableHelper.columnName(field), field.get(object));
1629           }
1630         }
1631         
1632         // The bind vars for the given object.
1633         List<Object> bindVarstoUse = new ArrayList<Object>();
1634 
1635         // Update if we are already saved.
1636         Boolean isUpdate = null;
1637         if (isInsert != null) {
1638           isUpdate = !isInsert;
1639         } else {
1640           if (this.isInsert) {
1641             isUpdate = false;
1642           } else {
1643             isUpdate = isPreviouslyPersisted.get(object);
1644           }
1645         }
1646         if (isUpdate){
1647 
1648           // Create the sql.
1649           if (!updateSqlInitialized){
1650             updateSql =  " update " + this.tableName(object.getClass()) + " set ";
1651             for (String columnName : columnNamesAndValues.keySet()){
1652               updateSql += " " + columnName + " = ?, " ;
1653             }
1654             updateSql = GrouperClientUtils.removeEnd(updateSql, ", ");
1655           }
1656 
1657           // Populate the bind vars.
1658           for (String columnName : columnNamesAndValues.keySet()){
1659             bindVarstoUse.add(columnNamesAndValues.get(columnName));
1660           }
1661 
1662           // If there is a primary key add that statement.
1663           if (primaryKey != null){
1664             if (!updateSqlInitialized){
1665               updateSql += " where " + GcPersistableHelper.columnName(primaryKey) + " = ? ";
1666             }
1667             bindVarstoUse.add(primaryKey.get(object));
1668           } else if (compoundPrimaryKeys.size() > 0){
1669 
1670             // There are multiple primary keys.
1671             if (!updateSqlInitialized){
1672               updateSql += " where ";
1673             }
1674 
1675             // Get all of the fields that are involved in the compound keys and build the sql and get bind variables.
1676             for (Field compoundPrimaryKey : compoundPrimaryKeys){
1677               Object fieldValue = null;
1678               try {
1679                 fieldValue = compoundPrimaryKey.get(object);
1680                 bindVarstoUse.add(fieldValue);
1681               } catch (Exception e) {
1682                 throw new RuntimeException(e);
1683               } 
1684               if (!updateSqlInitialized){
1685                 updateSql += GcPersistableHelper.columnName(compoundPrimaryKey) + " = ? and ";
1686               }
1687             }
1688             if (!updateSqlInitialized){
1689               updateSql = updateSql.substring(0, updateSql.length() - 4);
1690             }
1691           }
1692 
1693           // Store the fact that we made the sql and store the bind vars and sql.
1694           updateSqlInitialized = true;
1695           listsOfBindVars.add(bindVarstoUse);
1696 
1697         } else {
1698 
1699           // Else insert.
1700           String bindVarString = "";
1701           if (!insertSqlInitialized){
1702             insertSql =  " insert into " + this.tableName(object.getClass()) + " ( ";
1703             bindVarString = "values (";
1704             for (String columnName : columnNamesAndValues.keySet()){
1705               insertSql +=  columnName + "," ;
1706               bindVarString += "?,";
1707             }
1708           }
1709 
1710           for (String columnName : columnNamesAndValues.keySet()){
1711             bindVarstoUse.add(columnNamesAndValues.get(columnName));
1712           }
1713 
1714           // Get a primary key from the sequence if it is not manually assigned.
1715           if (!gcSqlAssignPrimaryKey && primaryKey != null && !GcPersistableHelper.primaryKeyManuallyAssigned(primaryKey) && !GcPersistableHelper.findPersistableClassAnnotation(object.getClass()).hasNoPrimaryKey()){
1716 
1717             // Make the sql.
1718             if (!insertSqlInitialized){
1719               insertSql += GcPersistableHelper.columnName(primaryKey);
1720               insertSql += ") ";
1721               if (!omitPrimaryKeyPopulation){
1722                 bindVarString += "?) ";
1723               } else {
1724                 bindVarString += GcPersistableHelper.primaryKeySequenceName(primaryKey) + ".nextval) ";
1725               }
1726             }
1727 
1728             // Get the primary key.
1729             if (!omitPrimaryKeyPopulation){
1730               Object primaryKeyValue = new GcDbAccess()
1731                   .connection(this.connection)
1732                   .sql(" select " + GcPersistableHelper.primaryKeySequenceName(primaryKey) + ".nextval from dual")
1733                   .select(primaryKey.getType().getClass());
1734               bindVarstoUse.add(primaryKeyValue);
1735               indexOfObjectAndPrimaryKeyToSet.put(objectIndex, primaryKeyValue);
1736             }
1737 
1738           } else {
1739             if (!insertSqlInitialized){
1740               insertSql = GrouperClientUtils.removeEnd(insertSql, ",") + ") ";
1741               bindVarString = GrouperClientUtils.removeEnd(bindVarString, ",") + ") ";
1742             }
1743           }
1744 
1745           if (!insertSqlInitialized){
1746             insertSql += bindVarString;
1747           }
1748 
1749           // Store the fact that we made the sql and store the bind vars and sql.
1750           insertSqlInitialized = true;
1751           listsOfBindVars.add(bindVarstoUse);
1752         }
1753 
1754         objectIndex++;
1755       }
1756 
1757 
1758       // See which sql we need to send it.
1759       if (updateSql != null && insertSql != null){
1760         throw new RuntimeException("It is not possible to mix updates and inserts in one batch; Statement supports it but not with bind variables so we do not support it.");
1761       } else if (updateSql == null && insertSql == null){
1762         throw new RuntimeException("No sql was created!");
1763       } 
1764 
1765 
1766       // Execute the sql.
1767       this.batchBindVars(listsOfBindVars);
1768       this.sql(updateSql != null ? updateSql : insertSql);
1769       this.executeBatchSql();
1770       this.sql(null);
1771       this.batchBindVars(null);
1772 
1773       // Set the primary keys if there were inserts and we got new ones.
1774       for (Integer objectIndexInList : indexOfObjectAndPrimaryKeyToSet.keySet()){
1775         boundDataConversion.setFieldValue(objects.get(objectIndexInList), primaryKey, indexOfObjectAndPrimaryKeyToSet.get(objectIndexInList));
1776       }
1777 
1778       for (T t : objects) {
1779         if (t instanceof GcDbVersionable) {
1780           ((GcDbVersionable)t).dbVersionReset();
1781         }
1782       }
1783       
1784     } catch (Exception e){
1785       throw new RuntimeException(e);
1786     }
1787   }
1788 
1789 
1790   /**
1791    * <pre>For each row of a given resultset, hydrate an object and pass it to the callback.</pre>
1792    * @param <T>
1793    * @param clazz is the type of thing passed to the entity callback.
1794    * @param entityCallback is the callback object that receives this dbAccess with a session set up. 
1795    */
1796   public <T> void callbackEntity(Class<T> clazz, GcEntityCallback<T> entityCallback){
1797     selectList(clazz, entityCallback);
1798   }
1799 
1800 
1801 
1802 
1803   /**
1804    * <pre>Use a transaction for all calls that happen within this callback. Upon success with no exceptions thrown,
1805    * commit is called automatically. Upon failure, rollback it called. You may also call dbAccess.setTransactionEnd()
1806    * within the callback block.</pre>
1807    * @param <T>  is the type of thing being returned.
1808    * @param transactionCallback is the callback object that receives this dbAccess with a session set up. 
1809    * @return the thing that you want to return.
1810    */
1811   public <T> T callbackTransaction(GcTransactionCallback<T> transactionCallback){
1812     long startNanos = System.nanoTime();
1813 
1814     ConnectionBean connectionBean = null;
1815     
1816     try{
1817 
1818       connectionBean = connection(true, true, this.connectionName, this.connectionProvided, this.connection);
1819       
1820       // Make a new connection.
1821       this.connection = connectionBean.getConnection();
1822 
1823       // Execute sub logic.
1824       T t = transactionCallback.callback(this);
1825 
1826       ConnectionBean.transactionEnd(connectionBean, GcTransactionEnd.commit, true, true, true);
1827       
1828       return t;
1829 
1830     } catch (Exception e){
1831       ConnectionBean.transactionEnd(connectionBean, GcTransactionEnd.rollback, true, true, true);
1832       throw new RuntimeException(e);
1833     } finally {
1834       ConnectionBean.closeIfStarted(connectionBean);
1835       GrouperClientUtils.performanceTimingAllDuration(GrouperClientUtils.PERFORMANCE_LOG_LABEL_SQL, System.nanoTime()-startNanos);
1836 
1837     }
1838     
1839   }
1840 
1841   /**
1842    * Select a map of something from the database - set sql() before calling - this will return a map with column name and column value - this should only select one row from the database.
1843    * @param keyClass is the class of the key.
1844    * @param valueClass is the class of the value.
1845    * @param <K> 
1846    * @param <V> 
1847    * @return the map or null if nothing is found..
1848    */
1849   @SuppressWarnings({ "unchecked", "rawtypes" })
1850   public <K, V> Map<K,V>  selectMap(Class<K> keyClass, Class<V> valueClass){
1851 
1852     List<Map> list = selectList(Map.class);
1853     if (list.size() == 0){
1854       return null;
1855     }
1856 
1857     if (list.size() > 1){
1858       throw new RuntimeException("Only one object expected but " + list.size() + " were returned for sql " + this.sql);
1859     }
1860 
1861     // If it is  a map with a string key, we'll be nice and put it into a case ignore map to make it easier on people.
1862     if (keyClass.equals(String.class)){
1863       Map mapToReturn = new GcCaseIgnoreHashMap();
1864       for (Object key : list.get(0).keySet()){
1865         mapToReturn.put(String.valueOf(key), boundDataConversion.getFieldValue(valueClass, list.get(0).get(key)));
1866       }
1867       return mapToReturn;
1868     }
1869 
1870     // Else just make sure that the data conversion from the database happens.
1871     Map mapToReturn = new HashMap<K, V>();
1872     for (Object key : list.get(0).keySet()){
1873       mapToReturn.put(key, boundDataConversion.getFieldValue(valueClass, list.get(0).get(key)));
1874     }
1875     return mapToReturn;
1876   }
1877 
1878 
1879 
1880 
1881   /**
1882    * Select a map of two column values from the database - set sql() before calling - the first column in the sql will be used for  the map keys and the second will be used for the map values.
1883    * @param keyClass is the class of the key.
1884    * @param valueClass is the class of the value.
1885    * @param <K> 
1886    * @param <V> 
1887    * @return the map or null if nothing is found..
1888    */
1889   @SuppressWarnings({ "unchecked", "rawtypes" })
1890   public <K, V> Map<K,V>  selectMapMultipleRows(Class<K> keyClass, Class<V> valueClass){
1891 
1892     List<Map> list = selectList(Map.class);
1893     if (list.size() == 0){
1894       return null;
1895     }
1896 
1897     Iterator columnNames = list.get(0).keySet().iterator();
1898     Object keyName = columnNames.next();
1899     Object valueName = columnNames.next();
1900 
1901 
1902     // Else just make sure that the data conversion from the database happens.
1903     Map<K, V> mapToReturn = new HashMap<K, V>();
1904     for (Map theMap : list){
1905       mapToReturn.put((K)theMap.get(keyName), boundDataConversion.getFieldValue(valueClass, theMap.get(valueName)));
1906     }
1907     return mapToReturn;
1908   }
1909 
1910 
1911 
1912 
1913   /**
1914    * Select a map of rows from the database with column name as key and valueClass as value (should be Object if types differ)  from the database - set sql() before calling
1915    * Example: select first_name, last_name, middle_name from person where rownum < 3:
1916    * 
1917    * List(0)
1918    * Map key      Map value
1919    * first_name   Fred
1920    * last_name    Jones
1921    * middle_name  Percival
1922    * List(1)
1923    * Map key      Map value
1924    * first_name   Jeanette
1925    * last_name    Shawna
1926    * middle_name  Percival
1927    * </pre>
1928    * 
1929    * @return the map or null if nothing is found..
1930    */
1931   @SuppressWarnings({ "unchecked", "rawtypes" })
1932   public List<GcCaseIgnoreHashMap>  selectListMap(){
1933 
1934     List<Map> list = selectList(Map.class);
1935 
1936     // Be nice and put it into a case ignore map to make it easier on people.
1937     List<GcCaseIgnoreHashMap> newList = new ArrayList<GcCaseIgnoreHashMap>();
1938     for (Map map : list){
1939       GcCaseIgnoreHashMapc/GcCaseIgnoreHashMap.html#GcCaseIgnoreHashMap">GcCaseIgnoreHashMap mapToReturn= new GcCaseIgnoreHashMap();
1940       mapToReturn.putAll(map);
1941       newList.add(mapToReturn);
1942     }
1943 
1944     return newList;
1945   }
1946 
1947 
1948   /**
1949    * <pre>Select a map of key : column name and value : column value from the database - set sql() before calling.
1950    * Example: select first_name, last_name, middle_name from person:
1951    * Map key      Map value
1952    * first_name   Fred
1953    * last_name    Jones
1954    * middle_name  Percival
1955    * </pre>
1956    * @return the map or null if nothing is found..
1957    */
1958   public GcCaseIgnoreHashMap selectMapMultipleColumnsOneRow(){
1959 
1960     List<GcCaseIgnoreHashMap> caseIgnoreHashMaps = selectListMap();
1961 
1962     if (caseIgnoreHashMaps.size() > 1){
1963       throw new RuntimeException("More than one row was returned for query " + this.sql);
1964     }
1965     if (caseIgnoreHashMaps.size() == 1){
1966       return caseIgnoreHashMaps.get(0);
1967     }
1968     return null;
1969   }
1970 
1971 
1972 
1973   /**
1974    * Select something from the database - either set sql() before calling or primaryKey() 
1975    * @param <T> is the type of object that will be returned.
1976    * @param clazz  is the type of object that will be returned.
1977    * @return anything.
1978    */
1979   @SuppressWarnings("unchecked")
1980   public <T>T  select (Class<T> clazz){
1981 
1982     // See if we are caching and we have it in cache.
1983     if (this.cacheMinutes != null){
1984       Object cachedObject = this.selectFromQueryCache(false, clazz);
1985       if (cachedObject != null){
1986         return (T)cachedObject;
1987       }
1988     }
1989 
1990     List<T> list = selectList(clazz, true);
1991     if (list.size() == 0){
1992       return null;
1993     }
1994 
1995     if (list.size() > 1){
1996       throw new RuntimeException("Only one object expected but " + list.size() + " were returned for sql " + this.sql);
1997     }
1998 
1999     // See if we are caching and store it in cache.
2000     if (this.cacheMinutes != null){
2001       this.populateQueryCache(clazz, list.get(0), false);
2002     }
2003 
2004     return list.get(0);
2005   }
2006 
2007 
2008   /**
2009    * Select something from the database - either set sql() before calling or primaryKey(...) 
2010    * @param <T> is the type of object that will be returned.
2011    * @param clazz  is the type of object that will be returned.
2012    * @return anything.
2013    */
2014   public <T> List<T> selectList (final Class<T> clazz){
2015     return selectList(clazz, false);
2016   }
2017   
2018   
2019 
2020   /**
2021    * Select something from the database - either set sql() before calling or primaryKey(...) 
2022    * @param <T> is the type of object that will be returned.
2023    * @param clazz  is the type of object that will be returned.
2024    * @param calledFromSelect is whether the calling method is select, just for caching purposes.
2025    * @return anything.
2026    */
2027   @SuppressWarnings("unchecked")
2028   private <T> List<T> selectList (final Class<T> clazz, boolean calledFromSelect){
2029     // See if we are caching and we have it in cache if we are not being called from select.
2030     if (!calledFromSelect){
2031       if (this.cacheMinutes != null){
2032         Object cachedObject = this.selectFromQueryCache(true, clazz);
2033         if (cachedObject != null){
2034           return (List<T>)cachedObject;
2035         }
2036       }
2037     }
2038 
2039 
2040     List<T> resultList = selectList(clazz, null);
2041 
2042     if (!calledFromSelect) {
2043       // See if we are caching and store it in cache.
2044       if (this.cacheMinutes != null){
2045         this.populateQueryCache(clazz, resultList, true);
2046       }
2047     }
2048 
2049     return resultList;
2050   }
2051 
2052   private static final Pattern wherePattern = Pattern.compile(".*\\swhere\\s.*", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
2053   
2054   /**
2055    * pass in a query like select * from table and pass in the selectMultipleColumnName and batchBindVars of all the rows to retrieve
2056    * @param <T>
2057    * @param clazz
2058    * @return
2059    */
2060   private <T> List<T> selectListByColumnName(Class<T> clazz) {
2061     
2062     List<T> result = new ArrayList<>();
2063     
2064     // one bind var in each record to retrieve
2065     int numberOfBatches = GrouperClientUtils.batchNumberOfBatches(GrouperClientUtils.length(bindVars), 1000, false);
2066     
2067     
2068     String selectMultipleColumnNameTemp = this.selectMultipleColumnName;
2069     this.selectMultipleColumnName = null; // this will reset the selectMultipleColumnName so that selectList call below 
2070     // don't call this method again. Otherwise it will go in endless recursive loop where selectListByColumnName calls selectList
2071     // and selectList calls selectListByColumnName and so on
2072     for (int batchIndex = 0; batchIndex<numberOfBatches; batchIndex++) {
2073       
2074       List<Object> batchOfBindVariables = GrouperClientUtils.batchList(bindVars, 1000, batchIndex);
2075       
2076       boolean containsWhere = wherePattern.matcher(sql).matches();
2077       StringBuilder sqlBuilder = new StringBuilder(sql);
2078       if (containsWhere) {
2079         sqlBuilder.append(" and ( ");
2080       } else {
2081         sqlBuilder.append(" where ");
2082       }
2083       
2084       GcDbAccess gcDbAccess = this.cloneDbAccess();
2085       
2086       for (int i=0; i<batchOfBindVariables.size(); i++) {
2087         if (i>0) {
2088           sqlBuilder.append(" or ");
2089         }
2090         sqlBuilder.append(selectMultipleColumnNameTemp).append(" = ? ");
2091       }
2092       
2093       if (containsWhere) {
2094         sqlBuilder.append(" ) ");
2095       }
2096       
2097       List<T> listOfRows = gcDbAccess.sql(sqlBuilder.toString()).bindVars(batchOfBindVariables)
2098           .selectList(clazz);
2099       result.addAll(listOfRows);
2100     }
2101     return result;
2102   }
2103   
2104   /**
2105    * Select something from the database - either set sql() before calling or primaryKey(...) 
2106    * @param <T> is the type of object that will be returned.
2107    * @param clazz  is the type of object that will be returned.
2108    * @param entityCallback is a callback made for each row of data hydrated to an entity, may be null if actually returning the list.
2109    * @return anything.
2110    */
2111   private  <T> List<T> selectList (final Class<T> clazz, final GcEntityCallback<T> entityCallback){
2112 
2113     // Can't select by primary key and sql at the same time.
2114     if ((this.primaryKeys != null && (this.sql != null || this.example != null))
2115         || (this.sql != null && (this.primaryKeys != null || this.example != null)) 
2116         || (this.example != null && (this.primaryKeys != null || this.sql != null))){
2117       throw new RuntimeException("Set sql(), primaryKey(), or example() but not more than one! primaryKey() will formulate sql.");
2118     }
2119 
2120     
2121     if (GrouperClientUtils.isNotBlank(this.selectMultipleColumnName)) {
2122       return selectListByColumnName(clazz);
2123     }
2124 
2125     // Get a list of the columns that we are selecting.
2126     List<String> columnNamesList = new ArrayList<String>();
2127     for (Field field : GcPersistableHelper.heirarchicalFields(clazz)){
2128       if (GcPersistableHelper.isSelect(field, clazz)){
2129         String columnName = GcPersistableHelper.columnName(field);
2130         columnNamesList.add(columnName);
2131       }
2132     }
2133     String columnNames = GrouperClientUtils.join(columnNamesList.iterator(), ",");
2134 
2135 
2136     // If we have a primary key or a list of primary keys, select by them.
2137     if (this.primaryKeys != null){
2138       if (this.bindVars != null){
2139         throw new RuntimeException("Set bindVars() or primaryKey() but not both! primaryKey() will formulate sql.");
2140       }
2141 
2142       // Get the primary key field.
2143       Field primaryKeyField = GcPersistableHelper.primaryKeyField(clazz);
2144 
2145       String theSql = " select " + columnNames + " from " + this.tableName(clazz) + " where " +   GcPersistableHelper.columnName(primaryKeyField);
2146       if (this.primaryKeys.size() == 1){
2147         theSql += " = ? ";
2148         this.bindVars(this.primaryKeys.get(0));
2149       } else if (this.primaryKeys.size() > 1) {
2150         theSql += " in (";
2151         for (int i = 0; i < this.primaryKeys.size(); i++){
2152           theSql += "?,";
2153         }
2154         theSql = GrouperClientUtils.removeEnd(theSql, ",") + ")";
2155         this.bindVars(this.primaryKeys);
2156       } 
2157       this.sql(theSql);
2158 
2159       // They used no sql and no primary key - that means that we are selecting everything from the table.
2160     } else if (this.sql == null && this.example == null){
2161       this.sql(" select " + columnNames + " from " + this.tableName(clazz));
2162     } else if (this.example != null){
2163 
2164       // Make the sql and bind variables.
2165       String theSql = " select * from " + this.tableName(clazz) + " where ";
2166       String whereClauseToUse = "";
2167       List<Object> bindVarstoUse = new ArrayList<Object>();
2168 
2169       // We are selecting by example, get all values from the example object and put them into the where clause.
2170       for (Field field: GcPersistableHelper.heirarchicalFields(clazz)){
2171         field.setAccessible(true);
2172         try{
2173           if (GcPersistableHelper.isSelect(field, clazz) && !GcPersistableHelper.isPrimaryKey(field)){
2174 
2175             // Get the value of the field.
2176             Object fieldValue = field.get(this.example);
2177 
2178             // See if we are omitting null values from the comparison.
2179             if (this.omitNullValuesForExample && fieldValue == null){
2180               continue;
2181             }
2182 
2183             // All strings get wrapped in to_char for comparison by example - this allows us to compare on clobs.
2184             String columnName = "";
2185             if (field.getType().equals(String.class)){
2186               columnName = "to_char(" + GcPersistableHelper.columnName(field) + ")";
2187               if (fieldValue != null){
2188                 bindVarstoUse.add(fieldValue);
2189               }
2190             } else if (field.getType().equals(Date.class)){
2191               columnName = "to_char(" + GcPersistableHelper.columnName(field) + ", 'MM/DD/YYYY HH24:MI:SS')";
2192               if (fieldValue != null){
2193                 bindVarstoUse.add(new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").format((Date)fieldValue));
2194               }
2195             } else {
2196               columnName =  GcPersistableHelper.columnName(field);
2197               if (fieldValue != null){
2198                 bindVarstoUse.add(fieldValue);
2199               }
2200             }
2201             String bindOrEquals = (fieldValue == null ? " is null and " : " = ? and ");
2202             whereClauseToUse += columnName + bindOrEquals;
2203 
2204           }
2205         } catch (Exception e){
2206           throw new RuntimeException("Issues encountered trying to read field " + field.getName() + " in class " + this.example.getClass(), e);
2207         }
2208       }
2209       whereClauseToUse = GrouperClientUtils.removeEnd(whereClauseToUse, "and ");
2210 
2211       theSql += whereClauseToUse;
2212       this.sql(theSql);
2213       this.bindVars(bindVarstoUse);
2214     } 
2215 
2216 
2217     // Callback on the resultset and create the list of objects, attempting
2218     // to assign to class fields if T is a PersistableBase, else assign directly to T.
2219     Long startTime = System.nanoTime();
2220     String sqlToRecord = this.sql;
2221     List<T> list = this.callbackResultSet(new GcResultSetCallback<List<T>>() {
2222 
2223       @Override
2224       public List<T> callback(ResultSet resultSet) throws Exception {
2225         List<T> theList = new ArrayList<T>();
2226 
2227         // If we are selecting abig list we don't want to have to check every field on every object
2228         // to see if we have a value in the resultSet, we just want to do it once, so store that here.
2229         Map<String, Boolean> fieldIsIncludedInResults = new HashMap<String, Boolean>();
2230 
2231         while (resultSet.next()){
2232 
2233           // This is either an entity callback or we are adding stuff to a list.
2234           if (entityCallback != null){
2235             T t = addObjectToList(clazz, fieldIsIncludedInResults, resultSet, null);
2236             boolean keepScrolling = entityCallback.callback(t);
2237             if (!keepScrolling){
2238               break;
2239             }
2240           } else {
2241             addObjectToList(clazz, fieldIsIncludedInResults, resultSet, theList);
2242           }
2243         }
2244 
2245         return theList;
2246       }
2247     });
2248     this.addQueryToQueriesAndMillis(sqlToRecord, startTime);
2249 
2250     for (T t : GrouperClientUtils.nonNull(list)) {
2251       if (t instanceof GcDbVersionable) {
2252         ((GcDbVersionable)t).dbVersionReset();
2253       }
2254     }
2255     
2256     return list;
2257   }
2258 
2259   /**
2260    * returned from connection call
2261    */
2262   public static class ConnectionBean {
2263     
2264     /**
2265      * If the connection is provided externally
2266      */
2267     private boolean connectionProvided;
2268 
2269     /**
2270      * If the connection is provided externally
2271      * @return
2272      */
2273     public boolean isConnectionProvided() {
2274       return connectionProvided;
2275     }
2276 
2277     /**
2278      * If the connection is provided externally
2279      * @param connectionProvided
2280      */
2281     public void setConnectionProvided(boolean connectionProvided) {
2282       this.connectionProvided = connectionProvided;
2283     }
2284 
2285     /**
2286      * if we are in a transaction
2287      */
2288     private boolean inTransaction;
2289     
2290     /**
2291      * if we are in a transaction
2292      * @return the inTransaction
2293      */
2294     public boolean isInTransaction() {
2295       return this.inTransaction;
2296     }
2297     
2298     /**
2299      * if we are in a transaction
2300      * @param inTransaction1 the inTransaction to set
2301      */
2302     public void setInTransaction(boolean inTransaction1) {
2303       this.inTransaction = inTransaction1;
2304     }
2305 
2306     /**
2307      * connection
2308      */
2309     private Connection connection;
2310     
2311     /**
2312      * @return the connection
2313      */
2314     public Connection getConnection() {
2315       return this.connection;
2316     }
2317 
2318     /**
2319      * @param connection1 the connection to set
2320      */
2321     public void setConnection(Connection connection1) {
2322       this.connection = connection1;
2323     }
2324 
2325     /**
2326      * if a transaction was started
2327      */
2328     private boolean transactionStarted;
2329     
2330     /**
2331      * if a transaction was started
2332      * @return the transactionStarted
2333      */
2334     public boolean isTransactionStarted() {
2335       return this.transactionStarted;
2336     }
2337     
2338     /**
2339      * if a transaction was started
2340      * @param transactionStarted1 the transactionStarted to set
2341      */
2342     public void setTransactionStarted(boolean transactionStarted1) {
2343       this.transactionStarted = transactionStarted1;
2344     }
2345 
2346     /**
2347      * if the connection was started or reused from threadlocal
2348      */
2349     private boolean connectionStarted;
2350     
2351     /**
2352      * if the connection was started or reused from threadlocal
2353      * @return the connectionStarted
2354      */
2355     public boolean isConnectionStarted() {
2356       return this.connectionStarted;
2357     }
2358     
2359     /**
2360      * @param connectionStarted1 the connectionStarted to set
2361      */
2362     public void setConnectionStarted(boolean connectionStarted1) {
2363       this.connectionStarted = connectionStarted1;
2364     }
2365 
2366     /**
2367      * end a transaction
2368      * @param connectionBean
2369      * @param transactionEnd
2370      * @param endOnlyIfStarted 
2371      * only a connection and just end it with commit...
2372      * @param errorIfNoTransaction 
2373      * @param endTransaction 
2374      */
2375     public static void transactionEnd(ConnectionBean connectionBean, 
2376         GcTransactionEnd transactionEnd, boolean endOnlyIfStarted, 
2377         boolean errorIfNoTransaction, boolean endTransaction) {
2378       
2379       if (connectionBean == null) {
2380         return;
2381       }
2382 
2383       if (connectionBean.isConnectionProvided()) {
2384         return;
2385       }
2386       
2387       if (!connectionBean.isInTransaction()) {
2388         if (errorIfNoTransaction) {
2389           throw new RuntimeException("Cannot end a transaction when not in a transaction!");
2390         }
2391         return;
2392       }
2393       
2394       if (endOnlyIfStarted && !connectionBean.isTransactionStarted()) {
2395         return;
2396       }
2397       if (endTransaction) {
2398         transactionThreadLocal.remove();
2399       }
2400       try {
2401         switch (transactionEnd) {
2402           case commit:
2403             connectionBean.connection.commit();
2404             connectionBean.setInTransaction(false);
2405             break;
2406   
2407           case rollback:
2408             connectionBean.connection.rollback();
2409             connectionBean.setInTransaction(false);
2410             
2411             break;
2412           default:
2413             throw new RuntimeException("Not expecting: " + transactionEnd);
2414         }
2415       } catch (SQLException sqle) {
2416         throw new RuntimeException("Error: " + transactionEnd + ", " + endOnlyIfStarted, sqle);
2417       }
2418 
2419       if (endTransaction) {
2420         try {
2421           connectionBean.connection.setAutoCommit(true);
2422         } catch (SQLException sqle) {
2423           throw new RuntimeException(sqle);
2424         }
2425       }
2426 
2427     }
2428     
2429     
2430     /**
2431      * close the connection if started
2432      * @param connectionBean 
2433      */
2434     public static void closeIfStarted(ConnectionBean connectionBean) {
2435       
2436       if (connectionBean != null && connectionBean.isConnectionProvided()) {
2437         return;
2438       }
2439       ConnectionBean.transactionEnd(connectionBean, GcTransactionEnd.rollback, true, false, true);
2440 
2441       if (connectionBean != null && connectionBean.isConnectionStarted()) {
2442         
2443         connectionThreadLocal.remove();
2444         GrouperClientUtils.closeQuietly(connectionBean.getConnection());
2445       }
2446     }
2447     
2448   }
2449 
2450   /**
2451    *  
2452    */
2453   private static final Log LOG = LogFactory.getLog(GcDbAccess.class);
2454   
2455   /**
2456    * keep connection in thread local, based on connection name
2457    */
2458   private static ThreadLocal<Map<String,Connection>> connectionThreadLocal = new ThreadLocal<Map<String,Connection>>();
2459 
2460   /**
2461    * if in transaction, true means readwrite, false means readonly
2462    */
2463   private static ThreadLocal<Boolean> transactionThreadLocal = new ThreadLocal<Boolean>();
2464   
2465   /**
2466    * get a connection to the oracle DB
2467    * @param needsTransaction 
2468    * @param startIfNotStarted generally you start if not started
2469    * @param connectionName name of connection in properties file
2470    * @param isConnectionProvided if this connection is provided externally
2471    * @param providedConnection the connection that is provided
2472    * @return a connectionbean which wraps the connection never return null
2473    */
2474   private static ConnectionBean connection(boolean needsTransaction, boolean startIfNotStarted, String connectionName, boolean isConnectionProvided, Connection providedConnection) {
2475 
2476     ConnectionBean connectionBean = new ConnectionBean();
2477     
2478     if (isConnectionProvided) {
2479       connectionBean.setConnection(providedConnection);
2480       try {
2481         connectionBean.setConnectionStarted(!providedConnection.isClosed());
2482         connectionBean.setInTransaction(providedConnection.getTransactionIsolation() != Connection.TRANSACTION_NONE);
2483         connectionBean.setTransactionStarted(providedConnection.getTransactionIsolation() != Connection.TRANSACTION_NONE);
2484         connectionBean.setConnectionProvided(true);
2485       } catch (SQLException sqle) {
2486         throw new RuntimeException("error", sqle);
2487       }
2488       return connectionBean;
2489     }
2490     
2491     if (GrouperClientUtils.isBlank(connectionName)) {
2492       connectionName = GrouperClientUtils.defaultIfBlank(connectionName, GrouperClientConfig.retrieveConfig().propertyValueStringRequired("grouperClient.jdbc.defaultName"));
2493     }
2494     
2495     Map<String,Connection> connectionMapByName = connectionThreadLocal.get();
2496 
2497     //make sure this gets set once and correctly
2498     if (connectionMapByName == null) {
2499       synchronized (GcDbAccess.class) {
2500         connectionMapByName = connectionThreadLocal.get();
2501         if (connectionMapByName == null) {
2502           connectionMapByName = new HashMap<String, Connection>();
2503           connectionThreadLocal.set(connectionMapByName);
2504         }
2505       }
2506     }
2507     
2508     Connection connection = connectionMapByName.get(connectionName);
2509 
2510     //if no connection there and not starting if not started, just return
2511     if (connection == null && !startIfNotStarted) {
2512       return connectionBean;
2513     }
2514 
2515     //try to get it from threadlocal
2516     Boolean transaction = transactionThreadLocal.get();
2517 
2518     connectionBean.setInTransaction(needsTransaction || transaction != null);
2519     
2520     if (needsTransaction) {
2521       if (transaction != null) {
2522         connectionBean.setTransactionStarted(false);
2523       } else {
2524 
2525         transactionThreadLocal.set(true);
2526         
2527         connectionBean.setTransactionStarted(true);
2528       }
2529     }
2530     
2531     if (connection != null) {
2532       connectionBean.setConnectionStarted(false);
2533       connectionBean.setConnection(connection);
2534 
2535       //init to this
2536       try {
2537         if (connectionBean.isTransactionStarted()) {
2538           connection.setAutoCommit(false);
2539         }
2540       } catch (SQLException sqle) {
2541         throw new RuntimeException(sqle);
2542       }
2543       
2544       return connectionBean;
2545     }
2546 
2547     if (transaction != null) {
2548       throw new RuntimeException("How can you have a transaction without a connection???");
2549     }
2550     connectionBean.setConnectionStarted(true);
2551     
2552     final String[] url = new String[1];
2553     
2554     try {
2555       connection = connectionHelper(connectionName, url);
2556 
2557       connectionMapByName.put(connectionName, connection);
2558       connectionBean.setConnection(connection);
2559 
2560       if (connectionBean.isTransactionStarted()) {
2561         connection.setAutoCommit(false);
2562       } else {
2563         //init to this
2564         connection.setAutoCommit(true);
2565       }
2566 
2567       return connectionBean;
2568 
2569     } catch (Exception e) {
2570       connectionThreadLocal.remove();
2571       transactionThreadLocal.remove();
2572       throw new RuntimeException("Error connecting to: " + url[0], e);
2573     }
2574   }
2575 
2576   /**
2577    * @param connectionName
2578    * @param url
2579    * @return the connection
2580    * @throws ClassNotFoundException
2581    * @throws SQLException
2582    */
2583   public static Connection connectionCreateNew(String connectionName, final String[] url)
2584       throws ClassNotFoundException, SQLException {
2585     Connection connection;
2586     String driver = GrouperClientConfig.retrieveConfig().propertyValueString("grouperClient.jdbc." + connectionName + ".driver");
2587 
2588     url[0] = GrouperClientConfig.retrieveConfig().propertyValueStringRequired("grouperClient.jdbc." + connectionName + ".url");
2589 
2590     if (StringUtils.isBlank(driver)) {
2591       driver = GrouperClientUtils.convertUrlToDriverClassIfNeeded(url[0], driver);
2592     }
2593       
2594     Class.forName(driver);
2595  
2596     String user = GrouperClientConfig.retrieveConfig().propertyValueStringRequired("grouperClient.jdbc." + connectionName + ".user");
2597     String pass = GrouperClientConfig.retrieveConfig().propertyValueString("grouperClient.jdbc." + connectionName + ".pass");
2598     pass = Morph.decryptIfFile(pass);
2599     connection = DriverManager.getConnection(url[0], user, pass);
2600     return connection;
2601   }
2602 
2603   /**
2604    * get a connection from a grouper pool
2605    * @param connectionName
2606    * @param url
2607    * @return the connection
2608    * @throws ClassNotFoundException
2609    * @throws SQLException
2610    */
2611   public static Connection connectionGetFromPool(String connectionName, final String[] url)
2612       throws ClassNotFoundException, SQLException {
2613     
2614     try {
2615       Object grouperLoaderDbInstance = grouperLoaderDbInstance(connectionName);
2616       Connection connection = (Connection)GrouperClientUtils.callMethod(grouperLoaderDbInstance, "connection");
2617       url[0] = (String)GrouperClientUtils.callMethod(grouperLoaderDbInstance, "getUrl");
2618       return connection;
2619       
2620     } catch (Exception e) {
2621       throw new RuntimeException("Error calling constructor and 'connection' on " + GROUPER_LOADER_DB_CLASSNAME, e);
2622     }
2623     
2624   }
2625 
2626   final static String GROUPER_LOADER_DB_CLASSNAME = "edu.internet2.middleware.grouper.app.loader.db.GrouperLoaderDb";
2627 
2628 
2629   /**
2630    * get a connection from a grouper pool
2631    * @param connectionName
2632    * @param url
2633    * @return the connection
2634    * @throws ClassNotFoundException
2635    * @throws SQLException
2636    */
2637   public static Object grouperLoaderDbInstance(String connectionName)
2638       throws ClassNotFoundException {
2639     
2640     try {
2641       Class grouperLoaderDbClass = GrouperClientUtils.forName(GROUPER_LOADER_DB_CLASSNAME);
2642       Constructor constructor = grouperLoaderDbClass.getConstructor(new Class[]{String.class});
2643       Object grouperLoaderDbInstance = constructor.newInstance(connectionName);
2644       return grouperLoaderDbInstance;
2645     } catch (Exception e) {
2646       throw new RuntimeException("Error calling constructor and 'connection' on " + GROUPER_LOADER_DB_CLASSNAME, e);
2647     }
2648     
2649   }
2650 
2651   /**
2652    * the quote or empty string if none
2653    * @param connectionName
2654    * @return
2655    */
2656   public static String quoteForColumnsInSql(String connectionName) {
2657     try {
2658       Object grouperLoaderDbInstance = grouperLoaderDbInstance(connectionName);
2659       return (String)GrouperClientUtils.callMethod(grouperLoaderDbInstance, "getQuoteForColumnsInSql");
2660       
2661     } catch (Exception e) {
2662       throw new RuntimeException("Error calling constructor and 'getQuoteForColumnsInSql' on " + GROUPER_LOADER_DB_CLASSNAME, e);
2663     }
2664   }
2665 
2666 
2667 
2668 
2669   /**
2670    * Callback to get a callableStatement - commit is called if there is no exception thrown, otherwise rollback is called.
2671    * @param <T> is what you are returning, must be a type but you can return null.
2672    * @param callableStatementCallback is the callback object.
2673    * @return whatever you return from the connection callback.
2674    */
2675   public  <T> T callbackCallableStatement (GcCallableStatementCallback<T> callableStatementCallback){
2676 
2677     long startNanos = System.nanoTime();
2678 
2679     CallableStatement callableStatement = null;
2680 
2681     ConnectionBean connectionBean = null;
2682     
2683     try{
2684 
2685       connectionBean = connection(false, true, this.connectionName, this.connectionProvided, this.connection);
2686       
2687       // Make a new connection.
2688       this.connection = connectionBean.getConnection();
2689       
2690       // Create the callable statement.
2691       callableStatement = this.connection.prepareCall(callableStatementCallback.getQuery());
2692       callableStatement.setFetchSize(1000);
2693 
2694       // Execute sub logic.
2695       Long startTime = System.nanoTime();
2696       T t = callableStatementCallback.callback(callableStatement);
2697 
2698       this.addQueryToQueriesAndMillis(callableStatementCallback.getQuery(), startTime);
2699 
2700       return t;
2701 
2702     } catch (Exception e){
2703       throw new RuntimeException(e);
2704     } finally{
2705       try{
2706         if (callableStatement != null){
2707           callableStatement.close();
2708         }
2709       } catch (Exception e){
2710         // Nothing to do here.
2711       }
2712       ConnectionBean.closeIfStarted(connectionBean);
2713       GrouperClientUtils.performanceTimingAllDuration(GrouperClientUtils.PERFORMANCE_LOG_LABEL_SQL, System.nanoTime()-startNanos);
2714 
2715     }
2716 
2717   }
2718 
2719 
2720 
2721   /**
2722    * Callback to get a preparedStatement - commit is called if there is no exception thrown, otherwise rollback is called.
2723    * @param <T> is what you are returning, must be a type but you can return null.
2724    * @param preparedStatementCallback is the callback object.
2725    * @return whatever you return from the connection callback.
2726    */
2727   public  <T> T callbackPreparedStatement (GcPreparedStatementCallback<T> preparedStatementCallback){
2728 
2729     long startNanos = System.nanoTime();
2730 
2731     PreparedStatement preparedStatement = null;
2732 
2733     ConnectionBean connectionBean = null;
2734     
2735     try{
2736 
2737       connectionBean = connection(false, true, this.connectionName, this.connectionProvided, this.connection);
2738       
2739       // Make a new connection.
2740       this.connection = connectionBean.getConnection();
2741 
2742       // Create the callable statement.
2743       preparedStatement = this.connection.prepareStatement(preparedStatementCallback.getQuery());
2744       preparedStatement.setFetchSize(1000);
2745 
2746       // Execute sub logic.
2747       Long startTime = System.nanoTime();
2748       // Add bind variables if we have them.
2749       if (this.bindVars != null){
2750         int i = 1;
2751         for (Object bindVar : this.bindVars){
2752           boundDataConversion.addBindVariableToStatement(preparedStatement, bindVar, i);
2753           i++;
2754         }
2755       }
2756       T t = preparedStatementCallback.callback(preparedStatement);
2757       this.addQueryToQueriesAndMillis(preparedStatementCallback.getQuery(), startTime);
2758 
2759       return t;
2760 
2761     } catch (Exception e){
2762       throw new RuntimeException(e);
2763     } finally{
2764       try{
2765         if (preparedStatement != null){
2766           preparedStatement.close();
2767         }
2768       } catch (Exception e){
2769         // Nothing to do here.
2770       }
2771       ConnectionBean.closeIfStarted(connectionBean);
2772       GrouperClientUtils.performanceTimingAllDuration(GrouperClientUtils.PERFORMANCE_LOG_LABEL_SQL, System.nanoTime()-startNanos);
2773 
2774     }
2775 
2776   }
2777 
2778 
2779 
2780 
2781   /**
2782    * Callback to get a connection - commit is called if there is no exception thrown, otherwise rollback is called.
2783    * @param <T> is what you are returning, must be a type but you can return null.
2784    * @param connectionCallback is the callback object.
2785    * @return whatever you return from the connection callback.
2786    */
2787   public  <T> T callbackConnection (GcConnectionCallback<T> connectionCallback){
2788 
2789     ConnectionBean connectionBean = null;
2790     long startNanos = System.nanoTime();
2791 
2792     try{
2793 
2794       connectionBean = connection(false, true, this.connectionName, this.connectionProvided, this.connection);
2795       
2796       // Make a new connection.
2797       this.connection = connectionBean.getConnection();
2798 
2799       //dont worry about autocommit
2800 
2801       // Execute sub logic.
2802       Long startTime = System.nanoTime();
2803       T t = connectionCallback.callback(this.connection);
2804       this.addQueryToQueriesAndMillis("Connection callback, SQL unknown", startTime);
2805 
2806       return t;
2807 
2808     } catch (Exception e){
2809       throw new RuntimeException(e);
2810     } finally {
2811       ConnectionBean.closeIfStarted(connectionBean);
2812       GrouperClientUtils.performanceTimingAllDuration(GrouperClientUtils.PERFORMANCE_LOG_LABEL_SQL, System.nanoTime()-startNanos);
2813 
2814     }
2815 
2816   }
2817 
2818 
2819 
2820   /**
2821    * Callback a resultSet.
2822    * @param <T> is the type of object that will be returned.
2823    * @param resultSetCallback is the object to callback.
2824    * @return anything return from the callback object.
2825    */
2826   public  <T> T callbackResultSet (GcResultSetCallback<T> resultSetCallback){
2827 
2828     threadLocalQueryCountIncrement(1);
2829     long startNanos = System.nanoTime();
2830     
2831     // At very least, we have to have sql and a connection.
2832     if (this.sql == null){
2833       throw new RuntimeException("You must set sql!");
2834     }
2835 
2836     PreparedStatement preparedStatement = null;
2837 
2838     ConnectionBean connectionBean = null;
2839     
2840     try{
2841 
2842       connectionBean = connection(false, true, this.connectionName, this.connectionProvided, this.connection);
2843       
2844       // Make a new connection.
2845       this.connection = connectionBean.getConnection();
2846 
2847       // Get the statement object that we are going to use.
2848       preparedStatement = this.connection.prepareStatement(this.sql);
2849       preparedStatement.setFetchSize(1000);
2850       String sqltoRecord = this.sql;
2851 
2852 
2853       // Set the query timeout if there is one.
2854       if (this.queryTimeoutSeconds != null){
2855         preparedStatement.setQueryTimeout(this.queryTimeoutSeconds);
2856       }
2857 
2858       // Add bind variables if we have them.
2859       if (this.bindVars != null){
2860         int i = 1;
2861         for (Object bindVar : this.bindVars){
2862           boundDataConversion.addBindVariableToStatement(preparedStatement, bindVar, i);
2863           i++;
2864         }
2865       }
2866 
2867       // Add batch bind variables if we have them.
2868       if(this.batchBindVars != null){
2869         for (List<Object> theBindVars : this.batchBindVars){
2870           int i = 1;
2871           for (Object bindVar : theBindVars){
2872             boundDataConversion.addBindVariableToStatement(preparedStatement, bindVar, i);
2873             i++;
2874           }
2875           preparedStatement.addBatch();
2876         }
2877       }
2878 
2879       // Internally, we use this without a resultset callback.
2880       if (resultSetCallback == null){
2881 
2882         // Add batch bind variables if we have them.
2883         if(this.batchBindVars != null){
2884           Long startTime = System.nanoTime();
2885           this.numberOfBatchRowsAffected = preparedStatement.executeBatch();
2886           this.addQueryToQueriesAndMillis(sqltoRecord, startTime);
2887           return null;
2888         } 
2889 
2890         Long startTime = System.nanoTime();
2891         this.numberOfRowsAffected = preparedStatement.executeUpdate();
2892         this.addQueryToQueriesAndMillis(sqltoRecord, startTime);
2893         return null; 
2894       }
2895 
2896       // Externally, it is used as a callback.
2897       ResultSet rs = preparedStatement.executeQuery();
2898       this.resultSetMetaData = rs.getMetaData();
2899       return resultSetCallback.callback(rs);
2900       
2901     } catch (Exception e){
2902       throw new RuntimeException("sql: " + this.sql + ", " + (GrouperClientUtils.length(this.bindVars) > 1 ? ("args: " + GrouperClientUtils.toStringForLog(this.bindVars)) : ""), e);
2903     } finally {
2904       this.resultSetMetaData = null;
2905       this.resultSetMetadataColumnNameLowerToIndex = null;
2906       if (preparedStatement != null){
2907         try {
2908           preparedStatement.close();
2909         } catch (Exception e){
2910           throw new RuntimeException(e);
2911         }
2912       }
2913       ConnectionBean.closeIfStarted(connectionBean);
2914       GrouperClientUtils.performanceTimingAllDuration(GrouperClientUtils.PERFORMANCE_LOG_LABEL_SQL, System.nanoTime()-startNanos);
2915     }
2916   }
2917 
2918   private Map<String, Integer> resultSetMetadataColumnNameLowerToIndex = null;
2919   
2920   private ResultSetMetaData resultSetMetaData;
2921 
2922   /**
2923    * Execute some sql.
2924    * @return anything return from the callback object.
2925    */
2926   public int executeSql(){
2927     if (this.batchBindVars != null){
2928       throw new RuntimeException("Use executeBatchSql() with batchBindVars!");
2929     }
2930 
2931     callbackResultSet(null);
2932 
2933     return this.numberOfRowsAffected;
2934   }
2935 
2936 
2937   /**
2938    * Execute some sql as a batch.
2939    * @return anything return from the callback object.
2940    */
2941   public int[] executeBatchSql(){
2942     
2943     if (this.batchSize <= 0 || GrouperClientUtils.length(this.batchBindVars) <= this.batchSize) {
2944       
2945       if (this.bindVars != null){
2946         throw new RuntimeException("Use batchBindVars with executeBatchSql(), not bindVars!");
2947       }
2948       callbackResultSet(null);
2949       return this.numberOfBatchRowsAffected;
2950 
2951     }
2952     
2953     // we are batching
2954     int numberOfBatches = GrouperClientUtils.batchNumberOfBatches(this.batchBindVars, this.batchSize);
2955     int[] result = new int[GrouperClientUtils.length(this.batchBindVars)];
2956     for (int i=0;i<numberOfBatches;i++) {
2957       
2958       List<List<Object>> batchOfBindVars = GrouperClientUtils.batchList(this.batchBindVars, this.batchSize, i);
2959       GcDbAccess gcDbAccess = this.cloneDbAccess();
2960       gcDbAccess.batchBindVars(batchOfBindVars);
2961       gcDbAccess.batchSize(-1);
2962       int[] batchResult = gcDbAccess.executeBatchSql();
2963       System.arraycopy(batchResult, 0, result, i*this.batchSize, batchResult.length);
2964       
2965     }
2966     return result;
2967   }
2968 
2969 
2970   /**
2971    * Create the object of type T from the resultSet and add it to the list if the list is not null.
2972    * @param clazz is the class type to return.
2973    * @param fieldIsIncludedInResults is a map that allows us to check if the resultset field maps to the object only once for each query.
2974    * @param resultSet is the row of data.
2975    * @param theList is the list to add to if not null.
2976    * @param <T> is the class type to return.
2977    * @return the object.
2978    * @throws Exception 
2979    */
2980   private <T> T addObjectToList(Class<T> clazz, Map<String, Boolean> fieldIsIncludedInResults, ResultSet resultSet, List<T> theList) throws Exception{
2981     // We are either setting fields of a class that has at least one persistable annotation.
2982     if (GcPersistableHelper.hasPersistableAnnotation(clazz)){
2983 
2984       // Make a new instance to assign properties to.
2985       T t = clazz.newInstance();
2986 
2987       // Check each field of the class for persistability and try to assign if if possible.
2988       for (Field field : GcPersistableHelper.heirarchicalFields(clazz)){
2989         if (GcPersistableHelper.isSelect(field, clazz)){
2990           String columnName = GcPersistableHelper.columnName(field);
2991 
2992           // Make sure that we have the column data.
2993           Boolean columnInQueryResults = fieldIsIncludedInResults.get(columnName);
2994           if (columnInQueryResults == null){
2995             try {
2996               resultSet.findColumn(columnName);
2997               fieldIsIncludedInResults.put(columnName, new Boolean(true));
2998               columnInQueryResults = new Boolean(true);
2999             } catch (SQLException e){
3000               fieldIsIncludedInResults.put(columnName, new Boolean(false));
3001               columnInQueryResults = new Boolean(false);
3002             }
3003           }
3004           if (columnInQueryResults){
3005             int columnNumberOneIndexed = retrieveColumnNumberOneIndexed(columnName);
3006             Object value = retrieveObjectFromResultSetByIndex(resultSet, columnNumberOneIndexed);
3007             boundDataConversion.setFieldValue(t, field, value);
3008           }
3009         }
3010       }
3011       // Add the hydrated object to the list.
3012       if (theList != null){
3013         theList.add(t);
3014       }
3015       return t;
3016     } 
3017 
3018 
3019     // If someone is selecting a list of Map then we are just going to put the object and column name in the map.
3020     if (clazz.isAssignableFrom(Map.class)){
3021       int columnCount = resultSet.getMetaData().getColumnCount();
3022       Map<Object, Object> results = new LinkedHashMap<Object, Object>();
3023       for (int columnNumber = 1; columnNumber <= columnCount; columnNumber++){
3024         Object value = retrieveObjectFromResultSetByIndex(resultSet, columnNumber);
3025         results.put(resultSet.getMetaData().getColumnName(columnNumber).toLowerCase(), value);
3026       }
3027       @SuppressWarnings("unchecked")
3028       T t = (T)results;
3029       if (theList != null){
3030         theList.add(t);
3031       }
3032       return t;
3033     }
3034 
3035     // If someone is selecting a list of Map then we are just going to put the object and column name in the map.
3036     if (clazz.isAssignableFrom(Object[].class)){
3037       int columnCount = resultSet.getMetaData().getColumnCount();
3038       Object[] results = new Object[columnCount];
3039       for (int columnNumber = 1; columnNumber <= columnCount; columnNumber++){
3040         Object value = retrieveObjectFromResultSetByIndex(resultSet, columnNumber);
3041         results[columnNumber-1] = value;
3042       }
3043       @SuppressWarnings("unchecked")
3044       T t = (T)results;
3045       if (theList != null){
3046         theList.add(t);
3047       }
3048       return t;
3049     }
3050     // If someone is selecting a list of Map then we are just going to put the object and column name in the map.
3051     if (clazz.isAssignableFrom(String[].class)){
3052       int columnCount = resultSet.getMetaData().getColumnCount();
3053       String[] results = new String[columnCount];
3054       for (int columnNumber = 1; columnNumber <= columnCount; columnNumber++){
3055         Object value = retrieveObjectFromResultSetByIndex(resultSet, columnNumber);
3056         results[columnNumber-1] = boundDataConversion.getFieldValue(String.class, value);
3057       }
3058       @SuppressWarnings("unchecked")
3059       T t = (T)results;
3060       if (theList != null){
3061         theList.add(t);
3062       }
3063       return t;
3064     }
3065 
3066     // Or we are just returning a primitive or single object such as Long, etc.
3067     Object value = retrieveObjectFromResultSetByIndex(resultSet, 1);
3068     T t = boundDataConversion.getFieldValue(clazz, value);
3069     if (theList != null){
3070       theList.add(t);
3071     }
3072     return t;
3073 
3074 
3075   }
3076 
3077   /**
3078    * get a column index (1 indexed) from columnName
3079    * @param columnName
3080    * @return
3081    * @throws SQLException
3082    */
3083   private int retrieveColumnNumberOneIndexed(String columnName) throws SQLException {
3084     if (this.resultSetMetadataColumnNameLowerToIndex == null) {
3085       this.resultSetMetadataColumnNameLowerToIndex = new HashMap<String, Integer>();
3086       for (int columnOneIndex=1;columnOneIndex<=this.resultSetMetaData.getColumnCount();columnOneIndex++) {
3087         String columnLabel = this.resultSetMetaData.getColumnLabel(columnOneIndex);
3088         columnLabel = columnLabel.toLowerCase();
3089         
3090         if (this.resultSetMetadataColumnNameLowerToIndex.containsKey(columnLabel) && this.resultSetMetadataColumnNameLowerToIndex.get(columnLabel) != columnOneIndex) {
3091           throw new RuntimeException("Indexing " + columnLabel + " more than once!  Check sql.");
3092         }
3093         
3094         this.resultSetMetadataColumnNameLowerToIndex.put(columnLabel, columnOneIndex);
3095       }
3096     }
3097     columnName = columnName.toLowerCase();
3098     if (!this.resultSetMetadataColumnNameLowerToIndex.containsKey(columnName)) {
3099       throw new RuntimeException("Cant find column name '" + columnName + "' ! " + GrouperClientUtils.toStringForLog(this.resultSetMetadataColumnNameLowerToIndex));
3100     }
3101     return this.resultSetMetadataColumnNameLowerToIndex.get(columnName);
3102   }
3103   
3104   /**
3105    * get a cell value from database, will return string, long, double, timestamp
3106    * @param resultSet
3107    * @param columnNumberOneIndexed
3108    * @return
3109    * @throws SQLException
3110    */
3111   private Object retrieveObjectFromResultSetByIndex(ResultSet resultSet, int columnNumberOneIndexed) throws SQLException {
3112     int type = this.resultSetMetaData.getColumnType(columnNumberOneIndexed);
3113     switch (type) {
3114       case Types.BIGINT: 
3115       case Types.INTEGER:
3116       case Types.SMALLINT:
3117       case Types.TINYINT:
3118         
3119         BigDecimal bigDecimal = resultSet.getBigDecimal(columnNumberOneIndexed);
3120         //  if (bigDecimal == null) {
3121         //    return null;
3122         //  }
3123         //  return bigDecimal.longValue();
3124         return bigDecimal;
3125         
3126       case Types.DECIMAL:
3127       case Types.DOUBLE:
3128       case Types.FLOAT:
3129       case Types.NUMERIC:
3130       case Types.REAL:
3131        
3132         bigDecimal = resultSet.getBigDecimal(columnNumberOneIndexed);
3133         //  if (bigDecimal == null) {
3134         //    return null;
3135         //  }
3136         //  if (this.resultSetMetaData.getScale(columnNumberOneIndexed) == 0) {
3137         //    return bigDecimal.longValue();
3138         //  }
3139         // if we want to go down this path, need to check to see if has decimal and convert to long
3140         return bigDecimal;
3141         
3142       case Types.BIT:
3143       case Types.BOOLEAN:
3144         return resultSet.getBoolean(columnNumberOneIndexed);
3145         
3146       case Types.CHAR:
3147       case Types.VARCHAR:
3148       case Types.LONGVARCHAR:
3149       case Types.NCHAR:
3150       case Types.NVARCHAR:
3151       case Types.LONGNVARCHAR:
3152 
3153         return resultSet.getString(columnNumberOneIndexed);
3154 
3155       case Types.DATE:
3156       case Types.TIMESTAMP:
3157         
3158         return resultSet.getTimestamp(columnNumberOneIndexed);
3159 
3160       case Types.CLOB:
3161         Clob clob = resultSet.getClob(columnNumberOneIndexed);
3162         return clob != null ? clob.getSubString(1, (int) clob.length()) : null;
3163         
3164       case Types.OTHER: 
3165 
3166         return resultSet.getObject(columnNumberOneIndexed);
3167 
3168       default:
3169         throw new RuntimeException("Not expecting column type: " + type);
3170     }
3171     
3172   }
3173   
3174   /**
3175    * Cached queries, exposed mostly for testing, you should not need direct access to this.
3176    * @return the dbQueryCacheMap
3177    */
3178   public static Map<MultiKey, GcDbQueryCache> getGcDbQueryCacheMap() {
3179     return dbQueryCacheMap;
3180   }
3181 
3182 
3183   /**
3184    * The map containing reports if they have been turned on.
3185    * @return the queriesAndMillis
3186    */
3187   public static Map<String, GcQueryReport> getQueriesAndMillis() {
3188     return queriesAndMillis;
3189   }
3190 
3191 
3192 
3193   /**
3194    * Select the objects from the query cache.
3195    * @param isList is whether a list is being selected or not.
3196    * @param clazz is the type of thing being selected.
3197    * @return the cached object if it exists or null.
3198    */
3199   private Object selectFromQueryCache(boolean isList, Class<?> clazz){
3200     if (this.cacheMinutes == null || !GrouperClientUtils.isBlank(this.selectMultipleColumnName)){
3201       return null;
3202     }
3203     MultiKey queryKey = queryCacheKey(isList, clazz);
3204     GcDbQueryCache dbQueryCache = dbQueryCacheMap.get(queryKey);
3205     if (dbQueryCache == null){
3206       return null;
3207     }
3208     return dbQueryCache.getThingBeingCached();
3209   }
3210 
3211 
3212   /**
3213    * Set the object(s) to the query cache.
3214    * @param isList is whether a list is being selected or not.
3215    * @param clazz is the type of thing being selected.
3216    * @param thingBeingCached is the object(s) being cached.
3217    */
3218   private void populateQueryCache(Class<?> clazz, Object thingBeingCached, boolean isList){
3219     if (this.cacheMinutes == null || !GrouperClientUtils.isBlank(this.selectMultipleColumnName)){
3220       return;
3221     }
3222     MultiKey queryKey = this.queryCacheKey(isList, clazz);
3223     dbQueryCacheMap.put(queryKey, new GcDbQueryCache(this.cacheMinutes, thingBeingCached));
3224   }
3225 
3226 
3227   /**
3228    * A key unique to the current state of this dbaccess.
3229    * @param isList is whether a list is being selected or not.
3230    * @param clazz is the type of thing being selected.
3231    * @return the key.
3232    */
3233   private MultiKey queryCacheKey(boolean isList, Class<?> clazz){
3234     
3235     List<Object> key = new ArrayList<Object>();
3236     key.add(this.sql);
3237     key.add(clazz.getName());
3238     key.add(isList);
3239     //why is this in here?
3240     //key.add(queryTimeoutSeconds);
3241 
3242     if (this.bindVars != null && this.bindVars.size() > 0){
3243       for (Object bindVar : this.bindVars){
3244         key.add(bindVar);
3245       }
3246     }
3247     if (this.batchBindVars != null && this.batchBindVars.size() > 0){
3248       for (List<Object> bindVarList : this.batchBindVars){
3249         for (Object bindVar : bindVarList){
3250           key.add(bindVar);
3251         }
3252       }
3253     }
3254     if (this.primaryKeys != null){
3255       for (Object primaryKey : this.primaryKeys){
3256         key.add(primaryKey);
3257       }
3258     }
3259     return new MultiKey(key.toArray());
3260   }
3261 
3262 
3263   /**
3264    * Clone the existing dbAccess.
3265    * @return the cloned baccess.
3266    */
3267   public GcDbAccess cloneDbAccess(){
3268     GcDbAccessrClient/jdbc/GcDbAccess.html#GcDbAccess">GcDbAccess dbAccess = new GcDbAccess();
3269     for (Field field : GcDbAccess.class.getDeclaredFields()){
3270       if (Modifier.isStatic(field.getModifiers())) {
3271         continue;
3272       }
3273       try {
3274         field.setAccessible(true);
3275         field.set(dbAccess, field.get(this));
3276       } catch (Exception e) {
3277         throw new RuntimeException("Cannot clone value of field " + field.getName());
3278       } 
3279     }
3280     return dbAccess;
3281   }
3282 
3283   /**
3284    * @param connectionName
3285    * @param url
3286    * @return the connection
3287    * @throws ClassNotFoundException
3288    * @throws SQLException
3289    */
3290   public static Connection connectionHelper(String connectionName, final String[] url)
3291       throws ClassNotFoundException, SQLException {
3292     if (grouperIsStarted) {
3293       return connectionGetFromPool(connectionName, url);
3294     }
3295     try {
3296       
3297       // try anyways?
3298       return connectionGetFromPool(connectionName, url);
3299       
3300     } catch (Exception e) {
3301       // ignore
3302     }
3303     return connectionCreateNew(connectionName, url);
3304   }
3305 
3306   /**
3307    * pass in a connection to leverage transactions from caller
3308    * <pre>
3309    * HibernateSession.callbackHibernateSession(GrouperTransactionType.READ_WRITE_NEW, AuditControl.WILL_NOT_AUDIT, new HibernateHandler() {
3310    *  
3311    *  @Override
3312    *  public Object callback(HibernateHandlerBean hibernateHandlerBean)
3313    *      throws GrouperDAOException {
3314    *
3315    *    new GroupSave(grouperSession).assignCreateParentStemsIfNotExist(true).assignName("test:testGroup").save();
3316    *       
3317    *    Connection connection = ((SessionImpl)hibernateHandlerBean.getHibernateSession().getSession()).connection();
3318    *        
3319    *    new GcDbAccess().connection(connection).
3320    *      sql("insert into grouper_loader_log (id, job_name, status, job_type, started_time) values (?, ?, ?, ?, ?)").
3321    *      addBindVar(GrouperUuid.getUuid()).addBindVar("OTHER_JOB_attestationDaemon").addBindVar("SUCCESS").
3322    *      addBindVar("OTHER_JOB").addBindVar(new Timestamp(System.currentTimeMillis())).executeSql();
3323    *                
3324    *    return null;
3325    *  }
3326    * });
3327    * </pre>
3328    * @param connection
3329    * @return
3330    */
3331   public GcDbAccess connection(Connection connection) {
3332     this.connection = connection;
3333     
3334     if (connection != null) {
3335       this.connectionProvided = true;
3336     }
3337     
3338     return this;
3339   }
3340 }