1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.ibatis.sqlmap.engine.mapping.result;
17
18 import com.ibatis.common.beans.Probe;
19 import com.ibatis.common.beans.ProbeFactory;
20 import com.ibatis.common.jdbc.exception.NestedSQLException;
21 import com.ibatis.sqlmap.client.SqlMapException;
22 import com.ibatis.sqlmap.engine.exchange.DataExchange;
23 import com.ibatis.sqlmap.engine.impl.SqlMapClientImpl;
24 import com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate;
25 import com.ibatis.sqlmap.engine.mapping.result.loader.ResultLoader;
26 import com.ibatis.sqlmap.engine.mapping.sql.Sql;
27 import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement;
28 import com.ibatis.sqlmap.engine.scope.ErrorContext;
29 import com.ibatis.sqlmap.engine.scope.StatementScope;
30 import com.ibatis.sqlmap.engine.type.DomCollectionTypeMarker;
31 import com.ibatis.sqlmap.engine.type.DomTypeMarker;
32 import com.ibatis.sqlmap.engine.type.TypeHandler;
33 import com.ibatis.sqlmap.engine.type.TypeHandlerFactory;
34
35 import java.sql.ResultSet;
36 import java.sql.SQLException;
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.StringTokenizer;
46
47 import javax.xml.parsers.DocumentBuilderFactory;
48 import javax.xml.parsers.ParserConfigurationException;
49
50 import org.w3c.dom.Document;
51
52
53
54
55 public class ResultMap {
56
57
58 private static final Probe PROBE = ProbeFactory.getProbe();
59
60
61 private static final String KEY_SEPARATOR = "\002";
62
63
64 private String id;
65
66
67 private Class resultClass;
68
69
70
71 private ResultMapping[] resultMappings;
72
73
74 private ThreadLocal remappableResultMappings = new ThreadLocal();
75
76
77 private DataExchange dataExchange;
78
79
80 private List nestedResultMappings;
81
82
83 private Discriminator discriminator;
84
85
86 private Set groupByProps;
87
88
89 private String xmlName;
90
91
92 private String resource;
93
94
95 protected SqlMapExecutorDelegate delegate;
96
97
98 protected boolean allowRemapping = false;
99
100
101 public static final Object NO_VALUE = new Object();
102
103
104
105
106
107
108
109 public ResultMap(SqlMapExecutorDelegate delegate) {
110 this.delegate = delegate;
111 }
112
113
114
115
116
117
118 public SqlMapExecutorDelegate getDelegate() {
119 return delegate;
120 }
121
122
123
124
125
126
127 public String getId() {
128 return id;
129 }
130
131
132
133
134
135
136
137 public void setId(String id) {
138 this.id = id;
139 }
140
141
142
143
144
145
146 public Class getResultClass() {
147 return resultClass;
148 }
149
150
151
152
153
154
155
156
157
158
159
160 public Object getUniqueKey(String keyPrefix, Object[] values) {
161 if (groupByProps == null) {
162 return null;
163 }
164
165 StringBuilder keyBuffer;
166 if (keyPrefix != null) {
167 keyBuffer = new StringBuilder(keyPrefix);
168 } else {
169 keyBuffer = new StringBuilder();
170 }
171 for (int i = 0; i < getResultMappings().length; i++) {
172 String propertyName = getResultMappings()[i].getPropertyName();
173 if (groupByProps.contains(propertyName)) {
174 keyBuffer.append(values[i]);
175 keyBuffer.append('-');
176 }
177 }
178 if (keyBuffer.length() < 1) {
179 return null;
180 }
181
182 keyBuffer.append(KEY_SEPARATOR);
183 return keyBuffer.toString();
184 }
185
186
187
188
189
190
191
192
193
194 public Object getUniqueKey(Object[] values) {
195 return getUniqueKey(null, values);
196 }
197
198
199
200
201
202
203
204 public void setResultClass(Class resultClass) {
205 this.resultClass = resultClass;
206 }
207
208
209
210
211
212
213 public DataExchange getDataExchange() {
214 return dataExchange;
215 }
216
217
218
219
220
221
222
223 public void setDataExchange(DataExchange dataExchange) {
224 this.dataExchange = dataExchange;
225 }
226
227
228
229
230
231
232 public String getXmlName() {
233 return xmlName;
234 }
235
236
237
238
239
240
241
242 public void setXmlName(String xmlName) {
243 this.xmlName = xmlName;
244 }
245
246
247
248
249
250
251 public String getResource() {
252 return resource;
253 }
254
255
256
257
258
259
260
261 public void setResource(String resource) {
262 this.resource = resource;
263 }
264
265
266
267
268
269
270
271 public void addGroupByProperty(String name) {
272 if (groupByProps == null) {
273 groupByProps = new HashSet<>();
274 }
275 groupByProps.add(name);
276 }
277
278
279
280
281
282
283 public boolean hasGroupBy() {
284 return groupByProps != null && groupByProps.size() > 0;
285 }
286
287
288
289
290
291
292 public Iterator groupByProps() {
293 return groupByProps.iterator();
294 }
295
296
297
298
299
300
301
302 public void addNestedResultMappings(ResultMapping mapping) {
303 if (nestedResultMappings == null) {
304 nestedResultMappings = new ArrayList<>();
305 }
306 nestedResultMappings.add(mapping);
307 }
308
309
310
311
312
313
314 public List getNestedResultMappings() {
315 return nestedResultMappings;
316 }
317
318
319
320
321
322
323 public ResultMapping[] getResultMappings() {
324 if (allowRemapping) {
325 return (ResultMapping[]) remappableResultMappings.get();
326 }
327 return resultMappings;
328 }
329
330
331
332
333
334
335
336 public void setDiscriminator(Discriminator discriminator) {
337 if (this.discriminator != null) {
338 throw new SqlMapException("A discriminator may only be set once per result map.");
339 }
340 this.discriminator = discriminator;
341 }
342
343
344
345
346
347
348 public Discriminator getDiscriminator() {
349 return discriminator;
350 }
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365 public ResultMap resolveSubMap(StatementScope statementScope, ResultSet rs) throws SQLException {
366 ResultMap subMap = this;
367 if (discriminator != null) {
368 ResultMapping mapping = discriminator.getResultMapping();
369 Object value = getPrimitiveResultMappingValue(rs, mapping);
370 if (value == null) {
371 value = doNullMapping(value, mapping);
372 }
373 subMap = discriminator.getSubMap(String.valueOf(value));
374 if (subMap == null) {
375 subMap = this;
376 } else if (subMap != this) {
377 subMap = subMap.resolveSubMap(statementScope, rs);
378 }
379 }
380 return subMap;
381 }
382
383
384
385
386
387
388
389 public void setResultMappingList(List resultMappingList) {
390 if (allowRemapping) {
391 this.remappableResultMappings.set(resultMappingList.toArray(new ResultMapping[resultMappingList.size()]));
392 } else {
393 this.resultMappings = (ResultMapping[]) resultMappingList.toArray(new ResultMapping[resultMappingList.size()]);
394 }
395
396 Map props = new HashMap<>();
397 props.put("map", this);
398 dataExchange = getDelegate().getDataExchangeFactory().getDataExchangeForClass(resultClass);
399 dataExchange.initialize(props);
400 }
401
402
403
404
405
406
407 public int getResultCount() {
408 return this.getResultMappings().length;
409 }
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424 public Object[] getResults(StatementScope statementScope, ResultSet rs) throws SQLException {
425 ErrorContext errorContext = statementScope.getErrorContext();
426 errorContext.setActivity("applying a result map");
427 errorContext.setObjectId(this.getId());
428 errorContext.setResource(this.getResource());
429 errorContext.setMoreInfo("Check the result map.");
430
431 boolean foundData = false;
432 Object[] columnValues = new Object[getResultMappings().length];
433 for (int i = 0; i < getResultMappings().length; i++) {
434 ResultMapping mapping = getResultMappings()[i];
435 errorContext.setMoreInfo(mapping.getErrorString());
436 if (mapping.getStatementName() != null) {
437 if (resultClass == null) {
438 throw new SqlMapException(
439 "The result class was null when trying to get results for ResultMap named " + getId() + ".");
440 }
441 if (Map.class.isAssignableFrom(resultClass)) {
442 Class javaType = mapping.getJavaType();
443 if (javaType == null) {
444 javaType = Object.class;
445 }
446 columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, javaType);
447 } else if (DomTypeMarker.class.isAssignableFrom(resultClass)) {
448 Class javaType = mapping.getJavaType();
449 if (javaType == null) {
450 javaType = DomTypeMarker.class;
451 }
452 columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, javaType);
453 } else {
454 Probe p = ProbeFactory.getProbe(resultClass);
455 Class type = p.getPropertyTypeForSetter(resultClass, mapping.getPropertyName());
456 columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, type);
457 }
458 foundData = foundData || columnValues[i] != null;
459 } else if (mapping.getNestedResultMapName() == null) {
460 columnValues[i] = getPrimitiveResultMappingValue(rs, mapping);
461 if (columnValues[i] == null) {
462 columnValues[i] = doNullMapping(columnValues[i], mapping);
463 } else {
464 foundData = true;
465 }
466 }
467 }
468
469 statementScope.setRowDataFound(foundData);
470
471 return columnValues;
472 }
473
474
475
476
477
478
479
480
481
482
483
484
485
486 public Object setResultObjectValues(StatementScope statementScope, Object resultObject, Object[] values) {
487 final String previousNestedKey = statementScope.getCurrentNestedKey();
488 String ukey = (String) getUniqueKey(statementScope.getCurrentNestedKey(), values);
489 Map uniqueKeys = statementScope.getUniqueKeys(this);
490 statementScope.setCurrentNestedKey(ukey);
491 if (uniqueKeys != null && uniqueKeys.containsKey(ukey)) {
492
493
494 resultObject = uniqueKeys.get(ukey);
495 applyNestedResultMap(statementScope, resultObject, values);
496 resultObject = NO_VALUE;
497 } else if (ukey == null || uniqueKeys == null || !uniqueKeys.containsKey(ukey)) {
498
499
500 resultObject = dataExchange.setData(statementScope, this, resultObject, values);
501
502 if (ukey != null) {
503 if (uniqueKeys == null) {
504 uniqueKeys = new HashMap<>();
505 statementScope.setUniqueKeys(this, uniqueKeys);
506 }
507 uniqueKeys.put(ukey, resultObject);
508 }
509 applyNestedResultMap(statementScope, resultObject, values);
510 } else {
511
512 resultObject = NO_VALUE;
513 }
514
515 statementScope.setCurrentNestedKey(previousNestedKey);
516 return resultObject;
517 }
518
519
520
521
522
523
524
525
526
527
528
529 private void applyNestedResultMap(StatementScope statementScope, Object resultObject, Object[] values) {
530 if (resultObject != null && resultObject != NO_VALUE && nestedResultMappings != null) {
531 for (Object nestedResultMapping : nestedResultMappings) {
532 ResultMapping resultMapping = (ResultMapping) nestedResultMapping;
533 setNestedResultMappingValue(resultMapping, statementScope, resultObject, values);
534 }
535 }
536 }
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556 protected void setNestedResultMappingValue(ResultMapping mapping, StatementScope statementScope, Object resultObject,
557 Object[] values) {
558 try {
559
560 String resultMapName = mapping.getNestedResultMapName();
561 ResultMap resultMap = getDelegate().getResultMap(resultMapName);
562
563 resultMap = resultMap.resolveSubMap(statementScope, statementScope.getResultSet());
564
565 Class type = mapping.getJavaType();
566 String propertyName = mapping.getPropertyName();
567
568 Object obj = PROBE.getObject(resultObject, propertyName);
569
570 if (obj == null) {
571 if (type == null) {
572 type = PROBE.getPropertyTypeForSetter(resultObject, propertyName);
573 }
574
575 try {
576
577
578
579 if (Collection.class.isAssignableFrom(type)) {
580 obj = ResultObjectFactoryUtil.createObjectThroughFactory(type);
581 PROBE.setObject(resultObject, propertyName, obj);
582 }
583 } catch (Exception e) {
584 throw new SqlMapException(
585 "Error instantiating collection property for mapping '" + mapping.getPropertyName() + "'. Cause: " + e,
586 e);
587 }
588 }
589
590
591
592 boolean subResultObjectAbsent = false;
593 if (mapping.getNotNullColumn() != null
594 && statementScope.getResultSet().getObject(mapping.getNotNullColumn()) == null) {
595 subResultObjectAbsent = true;
596 }
597 if (!subResultObjectAbsent) {
598 values = resultMap.getResults(statementScope, statementScope.getResultSet());
599 if (statementScope.isRowDataFound()) {
600 Object o = resultMap.setResultObjectValues(statementScope, null, values);
601 if (o != NO_VALUE) {
602 if (obj instanceof Collection) {
603 ((Collection) obj).add(o);
604 } else {
605 PROBE.setObject(resultObject, propertyName, o);
606 }
607 }
608 }
609 }
610 } catch (SQLException e) {
611 throw new SqlMapException(
612 "Error getting nested result map values for '" + mapping.getPropertyName() + "'. Cause: " + e, e);
613 }
614 }
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633 protected Object getNestedSelectMappingValue(StatementScope statementScope, ResultSet rs, ResultMapping mapping,
634 Class targetType) throws SQLException {
635 try {
636 TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
637
638 String statementName = mapping.getStatementName();
639 SqlMapClientImpl client = (SqlMapClientImpl) statementScope.getSession().getSqlMapClient();
640
641 MappedStatement mappedStatement = client.getMappedStatement(statementName);
642 Class parameterType = mappedStatement.getParameterClass();
643 Object parameterObject = null;
644
645 if (parameterType == null) {
646 parameterObject = prepareBeanParameterObject(statementScope, rs, mapping, parameterType);
647 } else if (typeHandlerFactory.hasTypeHandler(parameterType)) {
648 parameterObject = preparePrimitiveParameterObject(rs, mapping, parameterType);
649 } else if (DomTypeMarker.class.isAssignableFrom(parameterType)) {
650 parameterObject = prepareDomParameterObject(rs, mapping);
651 } else {
652 parameterObject = prepareBeanParameterObject(statementScope, rs, mapping, parameterType);
653 }
654
655 Object result = null;
656 if (parameterObject != null) {
657
658 Sql sql = mappedStatement.getSql();
659 ResultMap resultMap = sql.getResultMap(statementScope, parameterObject);
660 Class resultClass = resultMap.getResultClass();
661
662 if (resultClass != null && !DomTypeMarker.class.isAssignableFrom(targetType)) {
663 if (DomCollectionTypeMarker.class.isAssignableFrom(resultClass)) {
664 targetType = DomCollectionTypeMarker.class;
665 } else if (DomTypeMarker.class.isAssignableFrom(resultClass)) {
666 targetType = DomTypeMarker.class;
667 }
668 }
669
670 result = ResultLoader.loadResult(client, statementName, parameterObject, targetType);
671
672 String nullValue = mapping.getNullValue();
673 if (result == null && nullValue != null) {
674 TypeHandler typeHandler = typeHandlerFactory.getTypeHandler(targetType);
675 if (typeHandler != null) {
676 result = typeHandler.valueOf(nullValue);
677 }
678 }
679 }
680 return result;
681 } catch (InstantiationException | IllegalAccessException e) {
682 throw new NestedSQLException("Error setting nested bean property. Cause: " + e, e);
683 }
684
685 }
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702 private Object preparePrimitiveParameterObject(ResultSet rs, ResultMapping mapping, Class parameterType)
703 throws SQLException {
704 TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
705 TypeHandler th = typeHandlerFactory.getTypeHandler(parameterType);
706 return th.getResult(rs, mapping.getColumnName());
707 }
708
709
710
711
712
713
714
715
716
717 private Document newDocument(String root) {
718 try {
719 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
720 doc.appendChild(doc.createElement(root));
721 return doc;
722 } catch (ParserConfigurationException e) {
723 throw new RuntimeException("Error creating XML document. Cause: " + e);
724 }
725 }
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740 private Object prepareDomParameterObject(ResultSet rs, ResultMapping mapping) throws SQLException {
741 TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
742
743 Document doc = newDocument("parameter");
744 Probe probe = ProbeFactory.getProbe(doc);
745
746 String complexName = mapping.getColumnName();
747
748 TypeHandler stringTypeHandler = typeHandlerFactory.getTypeHandler(String.class);
749 if (complexName.indexOf('=') > -1) {
750
751 StringTokenizer parser = new StringTokenizer(complexName, "{}=, ", false);
752 while (parser.hasMoreTokens()) {
753 String propName = parser.nextToken();
754 String colName = parser.nextToken();
755 Object propValue = stringTypeHandler.getResult(rs, colName);
756 probe.setObject(doc, propName, propValue.toString());
757 }
758 } else {
759
760 Object propValue = stringTypeHandler.getResult(rs, complexName);
761 probe.setObject(doc, "value", propValue.toString());
762 }
763
764 return doc;
765 }
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788 private Object prepareBeanParameterObject(StatementScope statementScope, ResultSet rs, ResultMapping mapping,
789 Class parameterType) throws InstantiationException, IllegalAccessException, SQLException {
790 TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
791
792 Object parameterObject;
793 if (parameterType == null) {
794 parameterObject = new HashMap<>();
795 } else {
796 parameterObject = ResultObjectFactoryUtil.createObjectThroughFactory(parameterType);
797 }
798 String complexName = mapping.getColumnName();
799
800 if (complexName.indexOf('=') > -1 || complexName.indexOf(',') > -1) {
801 StringTokenizer parser = new StringTokenizer(complexName, "{}=, ", false);
802 while (parser.hasMoreTokens()) {
803 String propName = parser.nextToken();
804 String colName = parser.nextToken();
805 Class propType = PROBE.getPropertyTypeForSetter(parameterObject, propName);
806 TypeHandler propTypeHandler = typeHandlerFactory.getTypeHandler(propType);
807 Object propValue = propTypeHandler.getResult(rs, colName);
808 PROBE.setObject(parameterObject, propName, propValue);
809 }
810 } else {
811
812 TypeHandler propTypeHandler = typeHandlerFactory.getTypeHandler(parameterType);
813 if (propTypeHandler == null) {
814 propTypeHandler = typeHandlerFactory.getUnkownTypeHandler();
815 }
816 parameterObject = propTypeHandler.getResult(rs, complexName);
817 }
818
819 return parameterObject;
820 }
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835 protected Object getPrimitiveResultMappingValue(ResultSet rs, ResultMapping mapping) throws SQLException {
836 Object value = null;
837 TypeHandler typeHandler = mapping.getTypeHandler();
838 if (typeHandler == null) {
839 throw new SqlMapException("No type handler could be found to map the property '" + mapping.getPropertyName()
840 + "' to the column '" + mapping.getColumnName()
841 + "'. One or both of the types, or the combination of types is not supported.");
842 }
843 String columnName = mapping.getColumnName();
844 int columnIndex = mapping.getColumnIndex();
845 if (columnName == null) {
846 value = typeHandler.getResult(rs, columnIndex);
847 } else {
848 value = typeHandler.getResult(rs, columnName);
849 }
850 return value;
851 }
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866 protected Object doNullMapping(Object value, ResultMapping mapping) throws SqlMapException {
867 if (value != null) {
868 return value;
869 }
870
871 TypeHandler typeHandler = mapping.getTypeHandler();
872 if (typeHandler != null) {
873 String nullValue = mapping.getNullValue();
874 if (nullValue != null) {
875 value = typeHandler.valueOf(nullValue);
876 }
877 return value;
878 }
879 throw new SqlMapException(
880 "No type handler could be found to map the property '" + mapping.getPropertyName() + "' to the column '"
881 + mapping.getColumnName() + "'. One or both of the types, or the combination of types is not supported.");
882 }
883 }