View Javadoc
1   /**
2    * Copyright 2014 Internet2
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  /**
17   * 
18   */
19  package edu.internet2.middleware.grouper.hibernate;
20  
21  import java.lang.reflect.Array;
22  import java.math.BigDecimal;
23  import java.sql.Connection;
24  import java.sql.PreparedStatement;
25  import java.sql.ResultSet;
26  import java.sql.SQLException;
27  import java.sql.Statement;
28  import java.sql.Timestamp;
29  import java.util.ArrayList;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.Date;
33  import java.util.HashSet;
34  import java.util.List;
35  import java.util.Set;
36  import java.util.regex.Matcher;
37  import java.util.regex.Pattern;
38  
39  import edu.internet2.middleware.grouperClient.collections.MultiKey;
40  import org.apache.commons.lang.StringUtils;
41  import org.apache.commons.logging.Log;
42  import org.hibernate.HibernateException;
43  import org.hibernate.Query;
44  import org.hibernate.ScrollableResults;
45  import org.hibernate.Session;
46  import org.hibernate.Transaction;
47  import org.hibernate.criterion.Criterion;
48  import org.hibernate.criterion.Junction;
49  import org.hibernate.criterion.Restrictions;
50  import org.hibernate.resource.transaction.spi.TransactionStatus;
51  import org.hibernate.type.ByteType;
52  import org.hibernate.type.CharacterType;
53  import org.hibernate.type.DateType;
54  import org.hibernate.type.DoubleType;
55  import org.hibernate.type.FloatType;
56  import org.hibernate.type.IntegerType;
57  import org.hibernate.type.LongType;
58  import org.hibernate.type.ObjectType;
59  import org.hibernate.type.ShortType;
60  import org.hibernate.type.StringType;
61  import org.hibernate.type.TimestampType;
62  import org.hibernate.type.TrueFalseType;
63  import org.hibernate.type.Type;
64  
65  import edu.internet2.middleware.grouper.Field;
66  import edu.internet2.middleware.grouper.internal.dao.QueryOptions;
67  import edu.internet2.middleware.grouper.util.GrouperUtil;
68  import edu.internet2.middleware.subject.Source;
69  import edu.internet2.middleware.subject.Subject;
70  
71  /**
72   * @author mchyzer
73   *
74   */
75  public class HibUtils {
76  
77    /**
78     * attach bind values to query
79     * @param query
80     */
81    public static void attachBindValues(Query query, List<HibernateParam> bindVarNameParams) {
82      //note, dont call the method bindVarNameParams() so it doesnt lazyload...
83      if (bindVarNameParams != null) {
84        for (HibernateParam hibernateParam : bindVarNameParams) {
85          
86          if (String.class.equals(hibernateParam.getType())) {
87            query.setString(hibernateParam.getName(), (String)hibernateParam.getValue());
88          } else if (Timestamp.class.equals(hibernateParam.getType())) {
89            query.setTimestamp(hibernateParam.getName(), (Date)hibernateParam.getValue());
90          } else if (Long.class.equals(hibernateParam.getType())) {
91            if (hibernateParam.getValue() == null) {
92              query.setBigDecimal(hibernateParam.getName(), null);
93            } else {
94              query.setLong(hibernateParam.getName(), (Long)hibernateParam.getValue());
95            }
96          } else if (Double.class.equals(hibernateParam.getType())) {
97            if (hibernateParam.getValue() == null) {
98              query.setBigDecimal(hibernateParam.getName(), null);
99            } else {
100             query.setDouble(hibernateParam.getName(), (Double)hibernateParam.getValue());
101           }
102         } else if (Integer.class.equals(hibernateParam.getType())) {
103           if (hibernateParam.getValue() == null) {
104             query.setBigDecimal(hibernateParam.getName(), null);
105           } else {
106             query.setInteger(hibernateParam.getName(), (Integer)hibernateParam.getValue());
107           }
108         } else {
109           throw new RuntimeException("Invalid bind var type: " 
110               + hibernateParam );
111         }
112       }
113     }
114   }
115 
116   /**
117    * Whether the type of the class means that it should be handled as a primitive
118    * query vs a hibernate object query.
119    * @param clazz is the class to check.
120    * @return true if so.
121    */
122   static boolean handleAsPrimitive(Class clazz) {
123     
124     if (clazz.isArray()) {
125       clazz = clazz.getComponentType();
126       
127       if (clazz == Object.class) {
128         return true;
129       }
130     }
131     
132     if (clazz.isPrimitive() || clazz == java.lang.String.class
133         || clazz == BigDecimal.class || clazz == Integer.class
134         || clazz == java.sql.Date.class || clazz == java.util.Date.class
135         || clazz == java.sql.Time.class || clazz == java.sql.Timestamp.class
136         || clazz == java.sql.Clob.class || clazz == java.sql.Blob.class
137         || clazz == java.sql.Ref.class || clazz == Boolean.class || clazz == Byte.class
138         || clazz == Short.class || clazz == Integer.class || clazz == Float.class
139         || clazz == Double.class || clazz == Long.class) {
140       return true;
141     }
142     return false;
143   }
144 
145   /**
146    * Convert the params to friendly strings
147    * 
148    * @param params
149    * @param types
150    * @return the string of the params (for logging)
151    */
152   @SuppressWarnings("unchecked")
153   public static String paramsToString(Object params, Object types) {
154     //nothing to do if nothing to do
155     if (params == null && types == null) {
156       return "null";
157     }
158 
159     List<Object> paramsList = GrouperUtil.toList(params);
160     List<Type> typesList = (List<Type>)(Object)GrouperUtil.toList(types);
161 
162     int paramLength = GrouperUtil.length(paramsList);
163     int typeLength = GrouperUtil.length(typesList);
164 
165     if (paramLength != typeLength) {
166       throw new RuntimeException("The params length " + paramLength
167           + " must equal the types length " + typeLength);
168     }
169     StringBuilder result = new StringBuilder();
170     //loop through, set the params
171     Type currentType = null;
172     for (int i = 0; i < paramLength; i++) {
173       try {
174         currentType = typesList.get(i);
175         result.append(currentType.toLoggableString(paramsList.get(i), null)).append(",");
176       } catch (HibernateException he) {
177         result.append("<error>");
178       }
179     }
180 
181     return result.toString();
182   }
183 
184   /**
185    * Parses an alias out of a sql query.
186    * @param text is the text to find the alias in.
187    * @param exceptionIfNotFound if true, when an alias is not found, and exception will be thrown.
188    * @return the alias.
189    */
190   public static String parseAlias(String text, boolean exceptionIfNotFound) {
191     Pattern pattern = Pattern.compile("\\{.*?\\}");
192     Matcher matcher = pattern.matcher(text);
193     String alias = null;
194     if (matcher.find()) {
195       alias = matcher.group();
196       alias = StringUtils.replace(alias, "{", "");
197       alias = StringUtils.replace(alias, "}", "");
198       if (alias != null && alias.indexOf(".") > -1) {
199         alias = alias.substring(0, alias.indexOf("."));
200       }
201       return alias;
202     }
203 
204     if (exceptionIfNotFound) {
205       throw new RuntimeException("Cannot find a sql alias in the text: " + text
206           + "An alias must be in the format {anything} or {anything.anything}. "
207           + "Each sql query must contain at least one alias.");
208     }
209     return null;
210   }
211 
212   /**
213    * if in an hql or sql query, depending on the value, pass is or = back
214    * @param value
215    * @param bindVar 
216    * @return the query comparator
217    */
218   public static String equalsOrIs(Object value, String bindVar) {
219     //if sent with colon, remove
220     if (!StringUtils.isBlank(bindVar) && bindVar.startsWith(":")) {
221       bindVar = bindVar.substring(1);
222     }
223     return value == null ? " is null " : (" = :" + bindVar + " ");
224   }
225 
226   /**
227    * if should allow cache
228    */
229   private static InheritableThreadLocal<Boolean> cachingEnabledThreadLocal = new InheritableThreadLocal<Boolean>();
230 
231   /**
232    * if should disallow cache
233    * <pre>
234    *  boolean needsUnassignment = HibUtils.assignDisallowCacheThreadLocal();
235    *  Set<AttributeAssign> attributeAssigns = null;
236    *  try {
237    *    attributeAssigns = retrieveAttributeAssignsByOwnerAndAttributeDefNameId(attributeDefName.getId());
238    *  } finally {
239    *    if (needsUnassignment) {
240    *      HibUtils.clearDisallowCacheThreadLocal();
241    *    }
242    *  }
243    * 
244    * </pre>
245    * @return if assigned
246    */
247   public static boolean assignDisallowCacheThreadLocal() {
248     Boolean caching = cachingEnabledThreadLocal.get();
249     if (caching != null && !caching) {
250       return false;
251     }
252     cachingEnabledThreadLocal.set(Boolean.FALSE);
253     return true;
254   }
255 
256   /**
257    * dont disallow cache anymore
258    */
259   public static void clearDisallowCacheThreadLocal() {
260     cachingEnabledThreadLocal.remove();
261   }
262   
263   /**
264    * 
265    * @param cacheable
266    * @param queryOptions
267    * @return if caching
268    */
269   public static boolean secondLevelCaching(Boolean cacheable, QueryOptions queryOptions) {
270 
271     {
272       //if set in threadlocal then dont cache
273       Boolean cachingEnabled = cachingEnabledThreadLocal.get();
274       if (cachingEnabled != null && !cachingEnabled) {
275         return false;
276       }
277       
278     }
279     
280     HibernateSession hibernateSession = HibernateSession._internal_hibernateSession();
281     
282     //if hibernate session says no, then no
283     if (hibernateSession != null && !hibernateSession.isCachingEnabled()) {
284       return false;
285     }
286     
287     //cant find answer
288     if (cacheable == null && (queryOptions == null || queryOptions.getSecondLevelCache() == null)) {
289       return false;
290     }
291     
292     //if no options, but has cacheable
293     if (queryOptions == null || queryOptions.getSecondLevelCache() == null) {
294       return cacheable;
295     }
296 
297     //this one trumps all if not null
298     return queryOptions.getSecondLevelCache();
299   }
300 
301   /**
302    * 
303    * @param cacheRegion
304    * @param queryOptions
305    * @return if caching
306    */
307   public static String secondLevelCacheRegion(String cacheRegion, QueryOptions queryOptions) {
308     if (StringUtils.isBlank(cacheRegion) && (queryOptions == null || StringUtils.isBlank(queryOptions.getSecondLevelCacheRegion()))) {
309       return null;
310     }
311     //if no options, but has cacheable
312     if (queryOptions == null || StringUtils.isBlank(queryOptions.getSecondLevelCacheRegion())) {
313       return cacheRegion;
314     }
315     //this one trumps all if not null
316     return queryOptions.getSecondLevelCacheRegion();
317     
318   }
319 
320   /**
321    * pattern to detect if a query starts with "from".  e.g. from Field
322    */
323   private static Pattern startsWithFrom = Pattern.compile("^\\s*from.*$", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
324   
325   /**
326    * pattern to detect if a query has a select and "from".  e.g. select field from Field field
327    */
328   private static Pattern hasSelectAndFrom = Pattern.compile("^\\s*select(.*?)(from.*)$", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
329   
330   /**
331    * convert an hql to a count hql
332    * @param hql
333    * @return the hql of the count query
334    */
335   public static String convertHqlToCountHql(String hql) {
336     
337     if (startsWithFrom.matcher(hql).matches()) {
338       return "select count(*) " + hql;
339     }
340     Matcher selectAndFromMatcher = hasSelectAndFrom.matcher(hql);
341     if (selectAndFromMatcher.matches()) {
342       String selectPart = selectAndFromMatcher.group(1);
343       String endOfQuery = selectAndFromMatcher.group(2);
344       // Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE, found ',' near line 1, column 16 [select count(aa,theGroup)
345       if (selectPart != null && selectPart.contains(",")) {
346         return "select count(*) " + endOfQuery;
347       }
348       return "select count( " + selectPart + " ) " + endOfQuery;
349     }
350     throw new RuntimeException("Cant convert query to count query: " + hql);
351   }
352 
353   /**
354    * logger 
355    */
356   private static final Log LOG = GrouperUtil.getLog(HibUtils.class);
357 
358   /**
359    * 
360    * @param scrollableResults
361    */
362   public static void closeQuietly(ScrollableResults scrollableResults) {
363     if (scrollableResults != null) {
364       try {
365         scrollableResults.close();
366       } catch (Exception e) {
367         //just log, something bad is happening
368         LOG.info("Problem closing scrollable results", e);
369       }
370     }
371   }
372   
373   /**
374    * find the property index based on property name
375    * @param propertyNames
376    * @param propertyName e.g. userId
377    * @return the index (0 based) in the data arrays where the object is
378    */
379   public static int propertyIndex(String[] propertyNames, String propertyName) {
380     int propertiesSize = GrouperUtil.length(propertyNames);
381     for (int i=0;i<propertiesSize;i++) {
382       if (StringUtils.equals(propertyNames[i], propertyName)) {
383         return i;
384       }
385     }
386     throw new RuntimeException("Cant find property: " + propertyName 
387         + " in list: " + GrouperUtil.toStringForLog(propertyNames));
388   }
389 
390   /**
391    * assign a property in hibernates arrays of states
392    * @param state
393    * @param propertyNames
394    * @param propertyName
395    * @param propertyValue
396    */
397   public static void assignProperty(Object[] state, String[] propertyNames, 
398       String propertyName, Object propertyValue) {
399     //first find which index
400     int propertyIndex = propertyIndex(propertyNames, propertyName);
401     //next assign the value
402     state[propertyIndex] = propertyValue;
403   }
404   
405   /**
406    * find a property value in hibernates arrays of states
407    * @param state
408    * @param propertyNames
409    * @param propertyName
410    * @return the object
411    */
412   public static Object propertyValue(Object[] state, String[] propertyNames, 
413       String propertyName) {
414     //first find which index
415     int propertyIndex = propertyIndex(propertyNames, propertyName);
416     //next get the value
417     return state[propertyIndex];
418   }
419   
420   /**
421    * close a prepared statement
422    * @param preparedStatement
423    */
424   public static void closeQuietly(PreparedStatement preparedStatement) {
425     try {
426       if (preparedStatement != null) {
427         preparedStatement.close();
428       }
429     } catch (Exception e) {
430       //forget about it
431     }
432   }
433 
434   /**
435    * <pre>
436    * evict a list of objects from hibernate.  do this always for two reasons:
437    * 1. If you edit an object that is in the hibernate session, and commit, it will
438    * commit those changes magically.  Only objects called session.save(obj) or 
439    * update etc should be committed
440    * 2. If you select an object, then try to store it back (but have a different
441    * reference, e.g. if the DTO went through it, then you will get an exception:
442    * "a different object with the same identifier value was already associated with the session"
443    * </pre>
444    * @param hibernateSession grouper hibernateSession, can be null if not known
445    * @param object to evict that was just retrieved, can be list or array
446    * @param onlyEvictIfNotNew true to only evict if this is a nested tx
447    */
448   public static void evict(HibernateSession hibernateSession,
449       Object object, boolean onlyEvictIfNotNew) {
450     evict(hibernateSession, object, onlyEvictIfNotNew, true);
451   }
452   
453   /**
454    * <pre>
455    * evict a list of objects from hibernate.  do this always for two reasons:
456    * 1. If you edit an object that is in the hibernate session, and commit, it will
457    * commit those changes magically.  Only objects called session.save(obj) or 
458    * update etc should be committed
459    * 2. If you select an object, then try to store it back (but have a different
460    * reference, e.g. if the DTO went through it, then you will get an exception:
461    * "a different object with the same identifier value was already associated with the session"
462    * </pre>
463    * @param hibernateSession grouper hibernateSession, can be null if not known
464    * @param object to evict that was just retrieved, can be list or array
465    * @param onlyEvictIfNotNew true to only evict if this is a nested tx
466    * @param evictBeforeFlush if evict before flush (dont do this if iterating through list)
467    */
468   public static void evict(HibernateSession hibernateSession,
469       Object object, boolean onlyEvictIfNotNew, boolean evictBeforeFlush) {
470     if (object instanceof Collection) {
471       HibUtils.evict(hibernateSession, (Collection)object, onlyEvictIfNotNew);
472       return;
473     }
474     
475     //dont worry about it if new and only evicting if not new
476     if (hibernateSession != null && hibernateSession.isNewHibernateSession() && onlyEvictIfNotNew) {
477       return;
478     }
479     
480     //if array, loop through
481     if (object != null && object.getClass().isArray()) {
482       if (evictBeforeFlush) {
483         hibernateSession.getSession().flush();
484       }
485       for (int i=0;i<Array.getLength(object);i++) {
486         HibUtils.evict(hibernateSession, Array.get(object, i), onlyEvictIfNotNew, false);
487       }
488       return;
489     }
490     
491     //not sure it could ever be null...
492     if (object != null) {
493       if (evictBeforeFlush) {
494         hibernateSession.getSession().flush();
495       }
496       
497       try {
498         hibernateSession.getSession().evict(object);
499       } catch (IllegalArgumentException e) {
500         // ignore
501         
502         // In hibernate 5, seems to sometimes throw:
503         // java.lang.IllegalArgumentException: Non-entity object instance passed to evict : cd620430b6204390be5e55e375a5cdb7
504         // at org.hibernate.event.internal.DefaultEvictEventListener.onEvict(DefaultEvictEventListener.java:95)
505         
506         // comment in hibernate 5 code:
507         
508         // see if the passed object is even an entity, and if not throw an exception
509         //              this is different than legacy Hibernate behavior, but what JPA 2.1 is calling for
510         //              with EntityManager.detach
511 
512         // TODO need to look at later
513       }
514     }
515   }
516   
517   /**
518    * <pre>
519    * evict a list of objects from hibernate.  do this always for two reasons:
520    * 1. If you edit an object that is in the hibernate session, and commit, it will
521    * commit those changes magically.  Only objects called session.save(obj) or 
522    * update etc should be committed
523    * 2. If you select an object, then try to store it back (but have a different
524    * reference, e.g. if the DTO went through it, then you will get an exception:
525    * "a different object with the same identifier value was already associated with the session"
526    * </pre>
527    * @param hibernateSession grouper hibernateSession
528    * @param list of objects from hibernate to evict
529    * @param onlyEvictIfNotNew true to only evict if this is a nested tx
530    */
531   public static void evict(HibernateSession hibernateSession,
532       Collection<Object> list, boolean onlyEvictIfNotNew) {
533     if (list == null) {
534       return;
535     }
536     hibernateSession.getSession().flush();
537     for (Object object : list) {
538       evict(hibernateSession, object, onlyEvictIfNotNew, false);
539     }
540   }
541 
542   /** 
543    * Logger
544    */
545   //private static FastLogger log = new FastLogger(HibUtils.class);
546   
547   /**
548    * make a list of criterions.  e.g. listCrit(crit1, crit2, etc).  will AND them together
549    * this is null and empty safe
550    * @param criterions
551    * @return the criterion containing the list or null if none passed in
552    */
553   public static Criterion listCrit(Criterion... criterions) {
554     return listCritHelper(Restrictions.conjunction(), criterions);
555   }
556 
557   /**
558    * make a list of criterions.  e.g. listCrit(critList).  will AND them together
559    * this is null and empty safe
560    * @param criterions
561    * @return the criterion containing the list or null if none passed in
562    */
563   public static Criterion listCrit(List<Criterion> criterions) {
564     return listCritHelper(Restrictions.conjunction(), GrouperUtil.toArray(criterions, Criterion.class));
565   }
566 
567   /**
568    * make a list of criterions.  e.g. listCrit(crit1, crit2, etc).  will OR them together
569    * this is null and empty safe
570    * @param criterions
571    * @return the criterion containing the list or null if none passed in
572    */
573   public static Criterion listCritOr(Criterion... criterions) {
574     return listCritHelper(Restrictions.disjunction(), criterions);
575   }
576 
577   /**
578    * make a list of criterions.  e.g. listCrit(crits).  will OR them together
579    * this is null and empty safe
580    * @param criterions
581    * @return the criterion containing the list or null if none passed in
582    */
583   public static Criterion listCritOr(List<Criterion> criterions) {
584     return listCritHelper(Restrictions.disjunction(), GrouperUtil.toArray(criterions, Criterion.class));
585   }
586 
587   /**
588    * make a list of criterions.  e.g. listCrit(crit1, crit2, etc).  will AND or OR them together
589    * this is null and empty safe
590    * @param junction either conjunction or disjunction
591    * @param criterions
592    * @return the criterion containing the list or null if none passed in
593    */
594   private static Criterion listCritHelper(Junction junction, Criterion... criterions) {
595     int criterionsLength = GrouperUtil.length(criterions);
596     if (criterionsLength == 0) {
597       return null;
598     }
599     
600     //if one no need for junction
601     if (criterionsLength == 1 && criterions[0] != null) {
602       return criterions[0];
603     }
604     
605     //count to see if any added
606     int resultsCount = 0;
607     for (int i=0;i<criterionsLength;i++) {
608       Criterion current = criterions[i];
609       if (current != null) {
610         resultsCount++;
611         junction.add(current);
612       }
613     }
614     if (resultsCount == 0) {
615       return null;
616     }
617     return junction;
618     
619   }
620   
621   /**
622    * close a connection null safe and dont throw exception
623    * @param connection
624    */
625   public static void closeQuietly(Connection connection) {
626     if (connection != null) {
627       try {
628         connection.close();
629       } catch (Exception e) {
630         //ignore
631       }
632     }
633   }
634 
635   /**
636    * close a resultSet null safe and dont throw exception
637    * @param resultSet
638    */
639   public static void closeQuietly(ResultSet resultSet) {
640     if (resultSet != null) {
641       try {
642         resultSet.close();
643       } catch (Exception e) {
644         //ignore
645       }
646     }
647   }
648 
649   /**
650    * close a session null safe and dont throw exception
651    * @param session
652    */
653   public static void closeQuietly(Session session) {
654     if (session != null) {
655       try {
656         session.close();
657       } catch (Exception e) {
658         //ignore
659       }
660     }
661   }
662 
663   /**
664    * close a statement null safe and dont throw exception
665    * @param statement
666    */
667   public static void closeQuietly(Statement statement) {
668     if (statement != null) {
669       try {
670         statement.close();
671       } catch (Exception e) {
672         //ignore
673       }
674     }
675   }
676 
677   /**
678    * rollback a connection quietly
679    * @param connection
680    */
681   public static void rollbackQuietly(Connection connection) {
682     if (connection != null) {
683       try {
684         connection.rollback();
685       } catch (Exception e) {
686         //ignore
687       }
688     }
689   }
690 
691   /**
692    * rollback a transaction quietly
693    * @param transaction
694    */
695   public static void rollbackQuietly(Transaction transaction) {
696     if (transaction != null && transaction.getStatus().isOneOf(TransactionStatus.ACTIVE)) {
697       try {
698         transaction.rollback();
699       } catch (Exception e) {
700         //ignore
701       }
702     }
703   }
704 
705   /**
706    * Attach params for a prepared statement.  The type of the params and types must be the
707    * same (e.g. either both array or list, but not one is Array, and the other list
708    * @param statement
709    * @param params either null, Object, Object[], or List of Objects
710    * @throws HibernateException
711    * @throws SQLException
712    * @deprecated doesnt work with postgres
713    */
714   @Deprecated
715   @SuppressWarnings("unchecked")
716   public static void attachParams(PreparedStatement statement, Object params)
717       throws HibernateException, SQLException {
718     if (GrouperUtil.length(params) == 0) {
719       return;
720     }
721     List<Object> paramList = listObject(params);
722     List<Type> typeList = hibernateTypes(paramList);
723     attachParams(statement, paramList, typeList);
724   }
725 
726   /**
727    * Attach params for a prepared statement.  The type of the params and types must be the
728    * same (e.g. either both array or list, but not one is Array, and the other list
729    * @param statement
730    * @param params either null, Object, Object[], or List of Objects
731    * @param types either null, Type, Type[], or List of Objects
732    * @throws HibernateException
733    * @throws SQLException
734    */
735   static void attachParams(PreparedStatement statement, Object params, Object types)
736       throws HibernateException, SQLException {
737     int paramLength = GrouperUtil.length(params);
738     int typeLength = GrouperUtil.length(types);
739     
740     //nothing to do if nothing to do
741     if (paramLength == 0 && typeLength == 0) {
742       return;
743     }
744   
745       if (paramLength != typeLength) {
746       throw new RuntimeException("The params length must equal the types length and params " +
747       "and types must either both or neither be null");
748       }
749     
750       List paramList = listObject(params);
751       List typeList = listObject(types);
752   
753       //loop through, set the params
754       Type currentType = null;
755       for (int i = 0; i < paramLength; i++) {
756         //not sure why the session implementer is null, if this ever fails for a type, 
757         //might want to not use hibernate and brute force it
758         currentType = (Type) typeList.get(i);
759         currentType.nullSafeSet(statement, paramList.get(i), i + 1, null);
760       }
761   
762   }
763 
764   /**
765    * convert an object to a list of objects
766    * @param object
767    * @return the list of objects
768    */
769   @SuppressWarnings("unchecked")
770   public static List<Object> listObject(Object object) {
771     
772     //if its already a list then we are all good
773     if (object instanceof List) {
774       return (List<Object>)object; 
775     }
776     
777     return GrouperUtil.toList(object);
778   
779   }
780 
781   /**
782    * convert types to a list of types
783    *       return ObjectType.INSTANCE;
784    * DoubleType.INSTANCE, LongType.INSTANCE, FloatType.INSTANCE, ByteType.INSTANCE, TrueFalseType.INSTANCE, 
785    * CharacterType.INSTANCE, ShortType.INSTANCE, DateType.INSTANCE, TimestampType.INSTANCE, StringType.INSTANCE;
786    * @param types
787    * @return the list of objects
788    */
789   @SuppressWarnings("unchecked")
790   public static List<Type> listType(Type... types) {
791     
792     if (types == null) {
793       return null;
794     }
795     
796     ArrayList<Type> typeList = new ArrayList<Type>();
797     
798     for(Type type : types) {
799       typeList.add(type);
800     }
801     
802     return typeList;
803   
804   }
805 
806   /**
807    * Returns a Hibernate Type for the given java type. Handles both primitives and Objects.
808    * Will throw an exception if the given object is null or if a type cannot be found for it.
809    * @param o is the object to find the Type for.
810    * @return the Type.
811    * @deprecated doesnt work with postgres
812    */
813   @Deprecated
814   public static Type hibernateType(Object o) {
815     if (o == null) {
816       //its possible to bind null (e.g. for an update), so just use object to do this
817       return ObjectType.INSTANCE;
818     }
819     Class clazz = o.getClass();
820   
821     if (clazz == int.class || o instanceof Integer) {
822       return IntegerType.INSTANCE;
823     } else if (clazz == double.class || clazz == Double.class) {
824       return DoubleType.INSTANCE;
825     } else if (clazz == long.class || clazz == Long.class) {
826       return LongType.INSTANCE;
827     } else if (clazz == float.class || clazz == Float.class) {
828       return FloatType.INSTANCE;
829     } else if (clazz == byte.class || clazz == Byte.class) {
830       return ByteType.INSTANCE;
831     } else if (clazz == boolean.class || clazz == Boolean.class) {
832       return TrueFalseType.INSTANCE;
833     } else if (clazz == char.class || clazz == Character.class) {
834       return CharacterType.INSTANCE;
835     } else if (clazz == short.class || clazz == Short.class) {
836       return ShortType.INSTANCE;
837     } else if (clazz == java.util.Date.class || clazz == java.sql.Date.class) {
838       //return Hibernate.TIMESTAMP;
839       return DateType.INSTANCE;
840     } else if (clazz == Timestamp.class) {
841       return TimestampType.INSTANCE;
842     } else if (clazz == String.class) {
843       return StringType.INSTANCE;
844     }
845     throw new RuntimeException(
846         "Cannot find a hibernate type to associate with java type " + clazz);
847   }
848   
849   /**
850    * Returns a list of Hibernate types corresponding to the given params.
851    * @param params are the objects to get the types for. Can be list, Object, or array.
852    * @return the corresponding types.
853    * @deprecated doesnt work with postgres
854    */
855   @Deprecated
856   public static List<Type> hibernateTypes(List<Object> params) {
857   
858     int length = GrouperUtil.length(params);
859   
860     //if null, make sure the same (or exception later)
861     if (length == 0) {
862       return null;
863     }
864   
865     // Get the types.
866     // Create a list of hibernate types.
867     List<Type> types = new ArrayList<Type>();
868   
869     for (int i = 0; i < length; i++) {
870       Object o = params.get(i);
871       types.add(hibernateType(o));
872     }
873     return types;
874   }
875 
876   /**
877    * convert a collection of strings (no parens) to an in clause
878    * @param collection
879    * @param scalarable to set the string
880    * @return the string of in clause (without parens)
881    */
882   public static String convertToInClause(Collection<String> collection, HqlQuery scalarable) {
883     
884     String unique = GrouperUtil.uniqueId();
885     
886     StringBuilder result = new StringBuilder();
887     int collectionSize = collection.size();
888     int i = 0;
889     for (String string : collection) {
890       String var = unique + i;
891       result.append(":" + var);
892 
893       //add to query
894       scalarable.setString(var, string);
895       if (i < collectionSize-1) {
896         result.append(", ");
897       }
898       i++;
899     }
900     return result.toString();
901   }
902   
903   /**
904    * convert a collection of strings (no parens) to an in clause
905    * @param collection
906    * @param scalarable to set the string
907    * @return the string of in clause (without parens)
908    */
909   public static String convertToInClauseAnyType(Collection<?> collection, HqlQuery scalarable) {
910     
911     String unique = GrouperUtil.uniqueId();
912     
913     StringBuilder result = new StringBuilder();
914     int collectionSize = collection.size();
915     int i = 0;
916     for (Object object : collection) {
917       String var = unique + i;
918       result.append(":" + var);
919 
920       //add to query
921       scalarable.setScalar(var, object);
922       if (i < collectionSize-1) {
923         result.append(", ");
924       }
925       i++;
926     }
927     return result.toString();
928   }
929   
930   /**
931    * @param memberAlias is the alias of the table for members, e.g. gm
932    * @param subjects collection of subjects
933    * @param hqlQuery so far
934    * @return the query, e.g.  ((gm.subject_id = '123' and gm.subject_source = 'jdbc') 
935    *   or (gm.subject_id = '234' and gm.subject_source = 'jdbc' ))
936    */
937   public static String convertToSubjectInClause(Collection<Subject> subjects, HqlQuery hqlQuery, String memberAlias) {
938     
939     //    ((gm.subject_id = '123' and gm.subject_source = 'jdbc') 
940     //      or (gm.subject_id = '234' and gm.subject_source = 'jdbc' ))
941     
942     String unique = GrouperUtil.uniqueId();
943     
944     StringBuilder result = new StringBuilder(" ( ");
945     int collectionSize = subjects.size();
946     int i = 0;
947     for (Subject subject : subjects) {
948       String subjectVar = unique + "_subj" + i;
949       String sourceVar = unique + "_source" + i;
950       result.append(" ( ").append(memberAlias).append(".subjectIdDb = :").append(subjectVar);
951       result.append(" and ").append(memberAlias).append(".subjectSourceIdDb = :").append(sourceVar).append(" ) ");
952 
953       //add to query
954       hqlQuery.setString(subjectVar, subject.getId());
955       hqlQuery.setString(sourceVar, subject.getSourceId());
956       if (i < collectionSize-1) {
957         result.append("\n or ");
958       }
959       i++;
960     }
961     result.append(" ) ");
962     return result.toString();
963 
964     
965   }
966 
967   /**
968    * escape the quotes from sql string
969    * @param input
970    * @return the escaped string
971    */
972   public static String escapeSqlString(String input) {
973     if (input == null) {
974       return input;
975     }
976     
977     return StringUtils.replace(input, "'", "''");
978     
979   }
980   
981   /**
982    * convert a collection of strings (no parens) to an in clause
983    * @param collection
984    * @return the string of in clause (without parens)
985    */
986   public static String convertToInClauseForSqlStatic(Collection<String> collection) {
987     
988     StringBuilder result = new StringBuilder();
989     int collectionSize = collection.size();
990     for (int i = 0; i < collectionSize; i++) {
991       result.append("?");
992 
993       if (i < collectionSize - 1) {
994         result.append(", ");
995       }
996     }
997     return result.toString();
998   }
999   
1000   /**
1001    * convert a collection of anything (no parens) to an in clause
1002    * @param collection
1003    * @return the string of in clause (without parens)
1004    */
1005   public static String convertToInClauseAnyTypeForSqlStatic(Collection<?> collection) {
1006     
1007     StringBuilder result = new StringBuilder();
1008     int collectionSize = collection.size();
1009     for (int i = 0; i < collectionSize; i++) {
1010       result.append("?");
1011 
1012       if (i < collectionSize - 1) {
1013         result.append(", ");
1014       }
1015     }
1016     return result.toString();
1017   }
1018 
1019 
1020   /**
1021    * Creates a Hibernate Criterion that breaks a (possibly very long) list of values into
1022    * an ORed series of IN statements, respecting the IN-statement-size-limit provided.
1023    *
1024    * See: https://thephilosophicalfacts.wordpress.com/2015/06/22/hibernate-criteria-list-size-is-more-than-1000-values/
1025    *
1026    * @param propertyName
1027    * @param values
1028    * @param inStatementSizeLimit
1029    * @return
1030    */
1031   public static Criterion buildInCriterion(String propertyName, List<?> values, int inStatementSizeLimit) {
1032     Criterion criterion = null;
1033     int listSize = values.size();
1034     for (int i = 0; i < listSize; i += inStatementSizeLimit) {
1035       List<?> subList;
1036       if (listSize > i + inStatementSizeLimit) {
1037         subList = values.subList(i, (i + inStatementSizeLimit));
1038       } else {
1039         subList = values.subList(i, listSize);
1040       }
1041       if (criterion != null) {
1042         criterion = Restrictions.or(criterion, Restrictions.in(propertyName, subList));
1043       } else {
1044         criterion = Restrictions.in(propertyName, subList);
1045       }
1046     }
1047     return criterion;
1048   }
1049 
1050 
1051   /**
1052    * convert a collection of multikeys to an in clause with multiple args.  currently this only
1053    * works with strings, though we could add support for more types in future
1054    * @param collection
1055    * @param scalarable to set the string
1056    * @param columnNames names of columns in multikey
1057    * @param whereClause
1058    */
1059   public static void convertToMultiKeyInClause(Collection<MultiKey> collection, HqlQuery scalarable, 
1060       Collection<String> columnNames, StringBuilder whereClause) {
1061     
1062     String unique = GrouperUtil.uniqueId();
1063     
1064     int collectionSize = collection.size();
1065     int columnNamesSize = columnNames.size();
1066     int i = 0;
1067     
1068     if (GrouperUtil.length(collection) == 0) {
1069       return;
1070     }
1071     
1072     whereClause.append(" and ( ");
1073     
1074     for (MultiKey multiKey : collection) {
1075       
1076       whereClause.append(" ( ");
1077       
1078       int j = 0;
1079       
1080       for (String columnName : columnNames) {
1081 
1082         String var = unique + i + "_" + j;
1083 
1084         whereClause.append(" ").append(columnName).append(" = :").append(var).append(" ");
1085         
1086         //add to query
1087         scalarable.setString(var, (String)multiKey.getKey(j));
1088 
1089         if (j < columnNamesSize-1) {
1090           whereClause.append(" and ");
1091         }
1092         j++;
1093 
1094       }
1095       
1096       whereClause.append(" ) ");
1097 
1098       if (i < collectionSize-1) {
1099         whereClause.append(" or ");
1100       }
1101       i++;
1102     }
1103     whereClause.append(" ) ");
1104   }
1105   
1106   /**
1107    * @param fields
1108    * @param hqlQuery 
1109    * @param sql before the and statement
1110    * @param fieldColumnName 
1111    */
1112   public static void convertFieldsToSqlInString(Collection<Field> fields, 
1113       HqlQuery hqlQuery, StringBuilder sql, String fieldColumnName) {
1114 
1115     if (GrouperUtil.length(fields) > 0) {
1116       if (GrouperUtil.length(fields) == 1) {
1117         String bindVar = "fieldId_" + GrouperUtil.uniqueId();
1118         sql.append(" and ").append(fieldColumnName).append(" = :").append(bindVar).append(" ");
1119         hqlQuery.setString(bindVar, fields.iterator().next().getUuid());
1120       } else {
1121         sql.append(" and ").append(fieldColumnName).append(" in ( ");
1122         Set<String> fieldIds = new HashSet<String>();
1123         for (Field field : fields) {
1124           fieldIds.add(field.getUuid());
1125         }
1126         String inClause = HibUtils.convertToInClause(fieldIds, hqlQuery);
1127         sql.append(inClause).append(" ) ");
1128         
1129       }
1130     }
1131   }      
1132   
1133   /**
1134    * @param sources
1135    * @param hqlQuery 
1136    * @param sql before the and statement
1137    * @param sourceColumnName 
1138    */
1139   public static void convertSourcesToSqlInString(Set<Source> sources, 
1140       HqlQuery hqlQuery, StringBuilder sql, String sourceColumnName) {
1141     if (sources == null || sources.size() == 0) {
1142       return;
1143     }
1144     
1145     //simplify if 1
1146     if (sources.size() == 1) {
1147       String bindVar = "sourceId_" + GrouperUtil.uniqueId();
1148       sql.append(" and ").append(sourceColumnName).append(" = :").append(bindVar).append(" ");
1149       hqlQuery.setString(bindVar, sources.iterator().next().getId());
1150       return;
1151     }
1152     
1153     List<String> sourcesStrings = new ArrayList<String>();
1154     for (Source source : sources) {
1155       sourcesStrings.add(source.getId());
1156     }
1157     
1158     String questions = HibUtils.convertToInClause(sourcesStrings, hqlQuery);
1159     
1160     sql.append(" and ").append(sourceColumnName).append(" in ( ").append(questions).append(" ) ");
1161     
1162   }
1163   
1164   /**
1165    * e.g. ('g:gsa', 'jdbc')
1166    * @param sources
1167    * @return the in string, of sources sorted alphabetically
1168    */
1169   public static String convertSourcesToSqlInString(Set<Source> sources) {
1170     if (sources == null || sources.size() == 0) {
1171       return null;
1172     }
1173     
1174     //simplify if 1
1175     if (sources.size() == 1) {
1176       return " ('" + sources.iterator().next().getId() + "') ";
1177     }
1178     
1179     List<String> sourcesStrings = new ArrayList<String>();
1180     for (Source source : sources) {
1181       sourcesStrings.add(source.getId());
1182     }
1183     
1184     //sort 
1185     Collections.sort(sourcesStrings);
1186     
1187     StringBuilder result = new StringBuilder();
1188     result.append(" (");
1189     for (int i=0;i<sourcesStrings.size();i++) {
1190       result.append("'").append(sourcesStrings.get(i)).append("'");
1191       if (i != sourcesStrings.size()-1) {
1192         result.append(", ");
1193       }
1194     }
1195     result.append(") ");
1196     return result.toString();
1197   }
1198   
1199 
1200 }