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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public class GcDbAccess {
64
65
66
67
68 private static boolean grouperIsStarted = false;
69
70
71
72
73
74
75 public static boolean isGrouperIsStarted() {
76 return grouperIsStarted;
77 }
78
79
80
81
82
83 public static void setGrouperIsStarted(boolean theGrouperIsStarted) {
84 GcDbAccess.grouperIsStarted = theGrouperIsStarted;
85 }
86
87
88
89
90
91 private static Map<MultiKey, GcDbQueryCache> dbQueryCacheMap = new GcDbQueryCacheMap();
92
93
94
95
96
97 private Integer cacheMinutes;
98
99
100
101
102 private static boolean accumulateQueryMillis;
103
104
105
106
107 private static Map<String, GcQueryReport> queriesAndMillis;
108
109
110
111
112
113 private Integer queryTimeoutSeconds;
114
115
116
117
118 private List<Object> bindVars;
119
120
121
122
123 private List<List<Object>> batchBindVars;
124
125
126
127
128 private int batchSize = -1;
129
130
131
132
133 private boolean isInsert = false;
134
135
136
137
138 private boolean retryBatchStoreFailures = false;
139
140
141
142
143 private boolean ignoreRetriedBatchStoreFailures = false;
144
145
146
147
148
149
150 public GcDbAccess batchSize(int theBatchSize) {
151 this.batchSize = theBatchSize;
152 return this;
153 }
154
155
156
157
158
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
172
173
174
175 public GcDbAccess isInsert(boolean isInsert) {
176 this.isInsert = isInsert;
177 return this;
178 }
179
180
181
182
183 private static InheritableThreadLocal<Integer> queryCount = new InheritableThreadLocal<Integer>();
184
185
186
187
188 public static void threadLocalQueryCountReset() {
189 queryCount.set(0);
190 }
191
192
193
194
195
196 public static int threadLocalQueryCountRetrieve() {
197 Integer queryCountInteger = queryCount.get();
198 return queryCountInteger == null ? 0 : queryCountInteger;
199 }
200
201
202
203
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
212
213 private String sql;
214
215
216
217
218
219
220
221
222
223
224 private String selectMultipleColumnName;
225
226
227
228
229 private String tableName;
230
231
232
233
234
235
236 public GcDbAccess tableName(String theTableName) {
237 this.tableName = theTableName;
238 return this;
239 }
240
241
242
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
253
254 private List<Object> primaryKeys;
255
256
257
258
259
260 private String connectionName;
261
262
263
264
265
266
267 public GcDbAccess connectionName(String theConnectionName) {
268 this.connectionName = theConnectionName;
269 this.connectionProvided = false;
270 return this;
271 }
272
273
274
275
276
277
278 public static void transactionEnd(GcTransactionEnd transactionEnd, boolean endOnlyIfStarted) {
279 transactionEnd(transactionEnd, endOnlyIfStarted, null, false);
280 }
281
282
283
284
285
286
287
288
289 public static void transactionEnd(GcTransactionEnd transactionEnd, boolean endOnlyIfStarted, String connectionName) {
290 transactionEnd(transactionEnd, endOnlyIfStarted, connectionName, false);
291 }
292
293
294
295
296
297
298
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
318
319 private boolean connectionProvided;
320
321
322
323
324 private Connection connection;
325
326
327
328
329 private Object example;
330
331
332
333
334
335 private boolean omitNullValuesForExample;
336
337
338
339
340
341 private int numberOfRowsAffected;
342
343
344
345
346
347 private int numberOfBatchRowsAffected[];
348
349
350
351
352
353
354 private static GcBoundDataConversion boundDataConversion = new GcBoundDataConversionImpl();
355
356
357
358
359
360 private static boolean dbConnectionClassesRegistered = false;
361
362
363
364
365
366
367
368 public static void loadBoundDataConversion(GcBoundDataConversion _boundDataConversion) {
369 boundDataConversion = _boundDataConversion;
370 }
371
372
373
374
375
376
377
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
399
400
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
423
424
425
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
440
441
442
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
458
459
460
461 public GcDbAccess batchBindVars(List<List<Object>> _batchBindVars){
462 this.batchBindVars = _batchBindVars;
463 return this;
464 }
465
466
467
468
469
470
471
472
473 public GcDbAccess cacheMinutes(Integer _cacheMinutes){
474 this.cacheMinutes = _cacheMinutes;
475 return this;
476 }
477
478
479
480
481
482
483
484 public GcDbAccess sql(String _sql){
485 this.sql = _sql;
486 return this;
487 }
488
489
490
491
492
493
494
495
496
497
498
499
500 public GcDbAccess selectMultipleColumnName(String selectMultipleColumnName){
501 this.selectMultipleColumnName = selectMultipleColumnName;
502 return this;
503 }
504
505
506
507
508
509
510 public GcDbAccess omitNullValuesForExample(){
511 this.omitNullValuesForExample = true;
512 return this;
513 }
514
515
516
517
518
519
520
521
522
523 public GcDbAccess example(Object _example){
524 this.example = _example;
525 return this;
526 }
527
528
529
530
531
532
533
534 public GcDbAccess queryTimeoutSeconds(Integer _queryTimeoutSeconds){
535 this.queryTimeoutSeconds = _queryTimeoutSeconds;
536 return this;
537 }
538
539
540
541
542
543
544
545
546
547
548
549
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
563
564
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
575
576
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
597
598
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
617
618
619
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
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
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
657 objectsToQueryDatabase.add(object);
658
659 } else if (fieldValue != null && fieldValue instanceof String) {
660
661 results.put(object, true);
662 } else {
663 try {
664
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
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
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
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
794
795
796
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());
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);
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
937
938
939
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
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
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
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
1020
1021
1022
1023
1024
1025 public <T> void storeListToDatabase(final List<T> objects){
1026 storeBatchToDatabase(objects, 200);
1027 }
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038 public <T> boolean storeToDatabase(T t){
1039
1040 if (t instanceof GcDbVersionable) {
1041
1042
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
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
1077 for (Field field: GcPersistableHelper.heirarchicalFields(t.getClass())){
1078 field.setAccessible(true);
1079
1080
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
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
1119
1120
1121
1122
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
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
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
1163 this.sql(sqlToUse.toString());
1164 this.bindVars(bindVarstoUse);
1165 this.executeSql();
1166
1167
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
1179
1180
1181
1182
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
1201 for (Field field: GcPersistableHelper.heirarchicalFields(t.getClass())){
1202 field.setAccessible(true);
1203
1204
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
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
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
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
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267 public <T> int storeBatchToDatabase(final List<T> objects, final int batchSize){
1268 return storeBatchToDatabase(objects, batchSize, false);
1269 }
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
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
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
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
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
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
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
1395 objectsToStore.add(OBJECTS.get(i));
1396
1397
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
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429 private <T> void storeBatchToDatabase(List<T> objects){
1430 storeBatchToDatabase(objects, false);
1431 }
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
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
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
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543 private <T> void storeBatchToDatabaseHelper(List<T> objects, boolean omitPrimaryKeyPopulation){
1544
1545
1546 if (objects == null || objects.size() == 0){
1547 return;
1548 }
1549
1550
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
1561 Field primaryKey = GcPersistableHelper.primaryKeyField(objects.get(0).getClass());
1562
1563
1564 List<Field> compoundPrimaryKeys = GcPersistableHelper.compoundPrimaryKeyFields(objects.get(0).getClass());
1565
1566
1567 List<Field> allFields = GcPersistableHelper.heirarchicalFields(objects.get(0).getClass());
1568
1569
1570 Map<Field, Boolean> fieldAndIncludeStatuses = new HashMap<Field, Boolean>();
1571
1572
1573 for (Field field : allFields){
1574 field.setAccessible(true);
1575
1576
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
1592 List<List<Object>> listsOfBindVars = new ArrayList<List<Object>>();
1593
1594
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
1626 for (Field field : allFields){
1627 if (fieldAndIncludeStatuses.get(field)){
1628 columnNamesAndValues.put(GcPersistableHelper.columnName(field), field.get(object));
1629 }
1630 }
1631
1632
1633 List<Object> bindVarstoUse = new ArrayList<Object>();
1634
1635
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
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
1658 for (String columnName : columnNamesAndValues.keySet()){
1659 bindVarstoUse.add(columnNamesAndValues.get(columnName));
1660 }
1661
1662
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
1671 if (!updateSqlInitialized){
1672 updateSql += " where ";
1673 }
1674
1675
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
1694 updateSqlInitialized = true;
1695 listsOfBindVars.add(bindVarstoUse);
1696
1697 } else {
1698
1699
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
1715 if (!gcSqlAssignPrimaryKey && primaryKey != null && !GcPersistableHelper.primaryKeyManuallyAssigned(primaryKey) && !GcPersistableHelper.findPersistableClassAnnotation(object.getClass()).hasNoPrimaryKey()){
1716
1717
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
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
1750 insertSqlInitialized = true;
1751 listsOfBindVars.add(bindVarstoUse);
1752 }
1753
1754 objectIndex++;
1755 }
1756
1757
1758
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
1767 this.batchBindVars(listsOfBindVars);
1768 this.sql(updateSql != null ? updateSql : insertSql);
1769 this.executeBatchSql();
1770 this.sql(null);
1771 this.batchBindVars(null);
1772
1773
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
1792
1793
1794
1795
1796 public <T> void callbackEntity(Class<T> clazz, GcEntityCallback<T> entityCallback){
1797 selectList(clazz, entityCallback);
1798 }
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
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
1821 this.connection = connectionBean.getConnection();
1822
1823
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
1843
1844
1845
1846
1847
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
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
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
1883
1884
1885
1886
1887
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
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
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931 @SuppressWarnings({ "unchecked", "rawtypes" })
1932 public List<GcCaseIgnoreHashMap> selectListMap(){
1933
1934 List<Map> list = selectList(Map.class);
1935
1936
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
1950
1951
1952
1953
1954
1955
1956
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
1975
1976
1977
1978
1979 @SuppressWarnings("unchecked")
1980 public <T>T select (Class<T> clazz){
1981
1982
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
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
2010
2011
2012
2013
2014 public <T> List<T> selectList (final Class<T> clazz){
2015 return selectList(clazz, false);
2016 }
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027 @SuppressWarnings("unchecked")
2028 private <T> List<T> selectList (final Class<T> clazz, boolean calledFromSelect){
2029
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
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
2056
2057
2058
2059
2060 private <T> List<T> selectListByColumnName(Class<T> clazz) {
2061
2062 List<T> result = new ArrayList<>();
2063
2064
2065 int numberOfBatches = GrouperClientUtils.batchNumberOfBatches(GrouperClientUtils.length(bindVars), 1000, false);
2066
2067
2068 String selectMultipleColumnNameTemp = this.selectMultipleColumnName;
2069 this.selectMultipleColumnName = null;
2070
2071
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
2106
2107
2108
2109
2110
2111 private <T> List<T> selectList (final Class<T> clazz, final GcEntityCallback<T> entityCallback){
2112
2113
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
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
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
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
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
2165 String theSql = " select * from " + this.tableName(clazz) + " where ";
2166 String whereClauseToUse = "";
2167 List<Object> bindVarstoUse = new ArrayList<Object>();
2168
2169
2170 for (Field field: GcPersistableHelper.heirarchicalFields(clazz)){
2171 field.setAccessible(true);
2172 try{
2173 if (GcPersistableHelper.isSelect(field, clazz) && !GcPersistableHelper.isPrimaryKey(field)){
2174
2175
2176 Object fieldValue = field.get(this.example);
2177
2178
2179 if (this.omitNullValuesForExample && fieldValue == null){
2180 continue;
2181 }
2182
2183
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
2218
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
2228
2229 Map<String, Boolean> fieldIsIncludedInResults = new HashMap<String, Boolean>();
2230
2231 while (resultSet.next()){
2232
2233
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
2261
2262 public static class ConnectionBean {
2263
2264
2265
2266
2267 private boolean connectionProvided;
2268
2269
2270
2271
2272
2273 public boolean isConnectionProvided() {
2274 return connectionProvided;
2275 }
2276
2277
2278
2279
2280
2281 public void setConnectionProvided(boolean connectionProvided) {
2282 this.connectionProvided = connectionProvided;
2283 }
2284
2285
2286
2287
2288 private boolean inTransaction;
2289
2290
2291
2292
2293
2294 public boolean isInTransaction() {
2295 return this.inTransaction;
2296 }
2297
2298
2299
2300
2301
2302 public void setInTransaction(boolean inTransaction1) {
2303 this.inTransaction = inTransaction1;
2304 }
2305
2306
2307
2308
2309 private Connection connection;
2310
2311
2312
2313
2314 public Connection getConnection() {
2315 return this.connection;
2316 }
2317
2318
2319
2320
2321 public void setConnection(Connection connection1) {
2322 this.connection = connection1;
2323 }
2324
2325
2326
2327
2328 private boolean transactionStarted;
2329
2330
2331
2332
2333
2334 public boolean isTransactionStarted() {
2335 return this.transactionStarted;
2336 }
2337
2338
2339
2340
2341
2342 public void setTransactionStarted(boolean transactionStarted1) {
2343 this.transactionStarted = transactionStarted1;
2344 }
2345
2346
2347
2348
2349 private boolean connectionStarted;
2350
2351
2352
2353
2354
2355 public boolean isConnectionStarted() {
2356 return this.connectionStarted;
2357 }
2358
2359
2360
2361
2362 public void setConnectionStarted(boolean connectionStarted1) {
2363 this.connectionStarted = connectionStarted1;
2364 }
2365
2366
2367
2368
2369
2370
2371
2372
2373
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
2432
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
2457
2458 private static ThreadLocal<Map<String,Connection>> connectionThreadLocal = new ThreadLocal<Map<String,Connection>>();
2459
2460
2461
2462
2463 private static ThreadLocal<Boolean> transactionThreadLocal = new ThreadLocal<Boolean>();
2464
2465
2466
2467
2468
2469
2470
2471
2472
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
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
2511 if (connection == null && !startIfNotStarted) {
2512 return connectionBean;
2513 }
2514
2515
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
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
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
2578
2579
2580
2581
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
2605
2606
2607
2608
2609
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
2631
2632
2633
2634
2635
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
2653
2654
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
2671
2672
2673
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
2688 this.connection = connectionBean.getConnection();
2689
2690
2691 callableStatement = this.connection.prepareCall(callableStatementCallback.getQuery());
2692 callableStatement.setFetchSize(1000);
2693
2694
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
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
2723
2724
2725
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
2740 this.connection = connectionBean.getConnection();
2741
2742
2743 preparedStatement = this.connection.prepareStatement(preparedStatementCallback.getQuery());
2744 preparedStatement.setFetchSize(1000);
2745
2746
2747 Long startTime = System.nanoTime();
2748
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
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
2783
2784
2785
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
2797 this.connection = connectionBean.getConnection();
2798
2799
2800
2801
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
2822
2823
2824
2825
2826 public <T> T callbackResultSet (GcResultSetCallback<T> resultSetCallback){
2827
2828 threadLocalQueryCountIncrement(1);
2829 long startNanos = System.nanoTime();
2830
2831
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
2845 this.connection = connectionBean.getConnection();
2846
2847
2848 preparedStatement = this.connection.prepareStatement(this.sql);
2849 preparedStatement.setFetchSize(1000);
2850 String sqltoRecord = this.sql;
2851
2852
2853
2854 if (this.queryTimeoutSeconds != null){
2855 preparedStatement.setQueryTimeout(this.queryTimeoutSeconds);
2856 }
2857
2858
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
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
2880 if (resultSetCallback == null){
2881
2882
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
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
2924
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
2939
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
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
2972
2973
2974
2975
2976
2977
2978
2979
2980 private <T> T addObjectToList(Class<T> clazz, Map<String, Boolean> fieldIsIncludedInResults, ResultSet resultSet, List<T> theList) throws Exception{
2981
2982 if (GcPersistableHelper.hasPersistableAnnotation(clazz)){
2983
2984
2985 T t = clazz.newInstance();
2986
2987
2988 for (Field field : GcPersistableHelper.heirarchicalFields(clazz)){
2989 if (GcPersistableHelper.isSelect(field, clazz)){
2990 String columnName = GcPersistableHelper.columnName(field);
2991
2992
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
3012 if (theList != null){
3013 theList.add(t);
3014 }
3015 return t;
3016 }
3017
3018
3019
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
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
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
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
3079
3080
3081
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
3106
3107
3108
3109
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
3121
3122
3123
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
3134
3135
3136
3137
3138
3139
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
3176
3177
3178 public static Map<MultiKey, GcDbQueryCache> getGcDbQueryCacheMap() {
3179 return dbQueryCacheMap;
3180 }
3181
3182
3183
3184
3185
3186
3187 public static Map<String, GcQueryReport> getQueriesAndMillis() {
3188 return queriesAndMillis;
3189 }
3190
3191
3192
3193
3194
3195
3196
3197
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
3214
3215
3216
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
3229
3230
3231
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
3240
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
3265
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
3285
3286
3287
3288
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
3298 return connectionGetFromPool(connectionName, url);
3299
3300 } catch (Exception e) {
3301
3302 }
3303 return connectionCreateNew(connectionName, url);
3304 }
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
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 }