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 StringBuilder keyBuffer;
163 if (keyPrefix != null)
164 keyBuffer = new StringBuilder(keyPrefix);
165 else
166 keyBuffer = new StringBuilder();
167 for (int i = 0; i < getResultMappings().length; i++) {
168 String propertyName = getResultMappings()[i].getPropertyName();
169 if (groupByProps.contains(propertyName)) {
170 keyBuffer.append(values[i]);
171 keyBuffer.append('-');
172 }
173 }
174 if (keyBuffer.length() < 1) {
175 return null;
176 } else {
177
178 keyBuffer.append(KEY_SEPARATOR);
179 return keyBuffer.toString();
180 }
181 } else {
182 return null;
183 }
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 } else {
327 return resultMappings;
328 }
329 }
330
331
332
333
334
335
336
337 public void setDiscriminator(Discriminator discriminator) {
338 if (this.discriminator != null) {
339 throw new SqlMapException("A discriminator may only be set once per result map.");
340 }
341 this.discriminator = discriminator;
342 }
343
344
345
346
347
348
349 public Discriminator getDiscriminator() {
350 return discriminator;
351 }
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366 public ResultMap resolveSubMap(StatementScope statementScope, ResultSet rs) throws SQLException {
367 ResultMap subMap = this;
368 if (discriminator != null) {
369 ResultMapping mapping = (ResultMapping) discriminator.getResultMapping();
370 Object value = getPrimitiveResultMappingValue(rs, mapping);
371 if (value == null) {
372 value = doNullMapping(value, mapping);
373 }
374 subMap = discriminator.getSubMap(String.valueOf(value));
375 if (subMap == null) {
376 subMap = this;
377 } else if (subMap != this) {
378 subMap = subMap.resolveSubMap(statementScope, rs);
379 }
380 }
381 return subMap;
382 }
383
384
385
386
387
388
389
390 public void setResultMappingList(List resultMappingList) {
391 if (allowRemapping) {
392 this.remappableResultMappings
393 .set((ResultMapping[]) resultMappingList.toArray(new ResultMapping[resultMappingList.size()]));
394 } else {
395 this.resultMappings = (ResultMapping[]) resultMappingList.toArray(new ResultMapping[resultMappingList.size()]);
396 }
397
398 Map props = new HashMap();
399 props.put("map", this);
400 dataExchange = getDelegate().getDataExchangeFactory().getDataExchangeForClass(resultClass);
401 dataExchange.initialize(props);
402 }
403
404
405
406
407
408
409 public int getResultCount() {
410 return this.getResultMappings().length;
411 }
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426 public Object[] getResults(StatementScope statementScope, ResultSet rs) throws SQLException {
427 ErrorContext errorContext = statementScope.getErrorContext();
428 errorContext.setActivity("applying a result map");
429 errorContext.setObjectId(this.getId());
430 errorContext.setResource(this.getResource());
431 errorContext.setMoreInfo("Check the result map.");
432
433 boolean foundData = false;
434 Object[] columnValues = new Object[getResultMappings().length];
435 for (int i = 0; i < getResultMappings().length; i++) {
436 ResultMapping mapping = (ResultMapping) getResultMappings()[i];
437 errorContext.setMoreInfo(mapping.getErrorString());
438 if (mapping.getStatementName() != null) {
439 if (resultClass == null) {
440 throw new SqlMapException(
441 "The result class was null when trying to get results for ResultMap named " + getId() + ".");
442 } else if (Map.class.isAssignableFrom(resultClass)) {
443 Class javaType = mapping.getJavaType();
444 if (javaType == null) {
445 javaType = Object.class;
446 }
447 columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, javaType);
448 } else if (DomTypeMarker.class.isAssignableFrom(resultClass)) {
449 Class javaType = mapping.getJavaType();
450 if (javaType == null) {
451 javaType = DomTypeMarker.class;
452 }
453 columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, javaType);
454 } else {
455 Probe p = ProbeFactory.getProbe(resultClass);
456 Class type = p.getPropertyTypeForSetter(resultClass, mapping.getPropertyName());
457 columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, type);
458 }
459 foundData = foundData || columnValues[i] != null;
460 } else if (mapping.getNestedResultMapName() == null) {
461 columnValues[i] = getPrimitiveResultMappingValue(rs, mapping);
462 if (columnValues[i] == null) {
463 columnValues[i] = doNullMapping(columnValues[i], mapping);
464 } else {
465 foundData = true;
466 }
467 }
468 }
469
470 statementScope.setRowDataFound(foundData);
471
472 return columnValues;
473 }
474
475
476
477
478
479
480
481
482
483
484
485
486
487 public Object setResultObjectValues(StatementScope statementScope, Object resultObject, Object[] values) {
488 final String previousNestedKey = statementScope.getCurrentNestedKey();
489 String ukey = (String) getUniqueKey(statementScope.getCurrentNestedKey(), values);
490 Map uniqueKeys = statementScope.getUniqueKeys(this);
491 statementScope.setCurrentNestedKey(ukey);
492 if (uniqueKeys != null && uniqueKeys.containsKey(ukey)) {
493
494
495 resultObject = uniqueKeys.get(ukey);
496 applyNestedResultMap(statementScope, resultObject, values);
497 resultObject = NO_VALUE;
498 } else if (ukey == null || uniqueKeys == null || !uniqueKeys.containsKey(ukey)) {
499
500
501 resultObject = dataExchange.setData(statementScope, this, resultObject, values);
502
503 if (ukey != null) {
504 if (uniqueKeys == null) {
505 uniqueKeys = new HashMap();
506 statementScope.setUniqueKeys(this, uniqueKeys);
507 }
508 uniqueKeys.put(ukey, resultObject);
509 }
510 applyNestedResultMap(statementScope, resultObject, values);
511 } else {
512
513 resultObject = NO_VALUE;
514 }
515
516 statementScope.setCurrentNestedKey(previousNestedKey);
517 return resultObject;
518 }
519
520
521
522
523
524
525
526
527
528
529
530 private void applyNestedResultMap(StatementScope statementScope, Object resultObject, Object[] values) {
531 if (resultObject != null && resultObject != NO_VALUE) {
532 if (nestedResultMappings != null) {
533 for (int i = 0, n = nestedResultMappings.size(); i < n; i++) {
534 ResultMapping resultMapping = (ResultMapping) nestedResultMappings.get(i);
535 setNestedResultMappingValue(resultMapping, statementScope, resultObject, values);
536 }
537 }
538 }
539 }
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559 protected void setNestedResultMappingValue(ResultMapping mapping, StatementScope statementScope, Object resultObject,
560 Object[] values) {
561 try {
562
563 String resultMapName = mapping.getNestedResultMapName();
564 ResultMap resultMap = getDelegate().getResultMap(resultMapName);
565
566 resultMap = resultMap.resolveSubMap(statementScope, statementScope.getResultSet());
567
568 Class type = mapping.getJavaType();
569 String propertyName = mapping.getPropertyName();
570
571 Object obj = PROBE.getObject(resultObject, propertyName);
572
573 if (obj == null) {
574 if (type == null) {
575 type = PROBE.getPropertyTypeForSetter(resultObject, propertyName);
576 }
577
578 try {
579
580
581
582 if (Collection.class.isAssignableFrom(type)) {
583 obj = ResultObjectFactoryUtil.createObjectThroughFactory(type);
584 PROBE.setObject(resultObject, propertyName, obj);
585 }
586 } catch (Exception e) {
587 throw new SqlMapException(
588 "Error instantiating collection property for mapping '" + mapping.getPropertyName() + "'. Cause: " + e,
589 e);
590 }
591 }
592
593
594
595 boolean subResultObjectAbsent = false;
596 if (mapping.getNotNullColumn() != null) {
597 if (statementScope.getResultSet().getObject(mapping.getNotNullColumn()) == null) {
598 subResultObjectAbsent = true;
599 }
600 }
601 if (!subResultObjectAbsent) {
602 values = resultMap.getResults(statementScope, statementScope.getResultSet());
603 if (statementScope.isRowDataFound()) {
604 Object o = resultMap.setResultObjectValues(statementScope, null, values);
605 if (o != NO_VALUE) {
606 if (obj != null && obj instanceof Collection) {
607 ((Collection) obj).add(o);
608 } else {
609 PROBE.setObject(resultObject, propertyName, o);
610 }
611 }
612 }
613 }
614 } catch (SQLException e) {
615 throw new SqlMapException(
616 "Error getting nested result map values for '" + mapping.getPropertyName() + "'. Cause: " + e, e);
617 }
618 }
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637 protected Object getNestedSelectMappingValue(StatementScope statementScope, ResultSet rs, ResultMapping mapping,
638 Class targetType) throws SQLException {
639 try {
640 TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
641
642 String statementName = mapping.getStatementName();
643 SqlMapClientImpl client = (SqlMapClientImpl) statementScope.getSession().getSqlMapClient();
644
645 MappedStatement mappedStatement = client.getMappedStatement(statementName);
646 Class parameterType = mappedStatement.getParameterClass();
647 Object parameterObject = null;
648
649 if (parameterType == null) {
650 parameterObject = prepareBeanParameterObject(statementScope, rs, mapping, parameterType);
651 } else {
652 if (typeHandlerFactory.hasTypeHandler(parameterType)) {
653 parameterObject = preparePrimitiveParameterObject(rs, mapping, parameterType);
654 } else if (DomTypeMarker.class.isAssignableFrom(parameterType)) {
655 parameterObject = prepareDomParameterObject(rs, mapping);
656 } else {
657 parameterObject = prepareBeanParameterObject(statementScope, rs, mapping, parameterType);
658 }
659 }
660
661 Object result = null;
662 if (parameterObject != null) {
663
664 Sql sql = mappedStatement.getSql();
665 ResultMap resultMap = sql.getResultMap(statementScope, parameterObject);
666 Class resultClass = resultMap.getResultClass();
667
668 if (resultClass != null && !DomTypeMarker.class.isAssignableFrom(targetType)) {
669 if (DomCollectionTypeMarker.class.isAssignableFrom(resultClass)) {
670 targetType = DomCollectionTypeMarker.class;
671 } else if (DomTypeMarker.class.isAssignableFrom(resultClass)) {
672 targetType = DomTypeMarker.class;
673 }
674 }
675
676 result = ResultLoader.loadResult(client, statementName, parameterObject, targetType);
677
678 String nullValue = mapping.getNullValue();
679 if (result == null && nullValue != null) {
680 TypeHandler typeHandler = typeHandlerFactory.getTypeHandler(targetType);
681 if (typeHandler != null) {
682 result = typeHandler.valueOf(nullValue);
683 }
684 }
685 }
686 return result;
687 } catch (InstantiationException e) {
688 throw new NestedSQLException("Error setting nested bean property. Cause: " + e, e);
689 } catch (IllegalAccessException e) {
690 throw new NestedSQLException("Error setting nested bean property. Cause: " + e, e);
691 }
692
693 }
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710 private Object preparePrimitiveParameterObject(ResultSet rs, ResultMapping mapping, Class parameterType)
711 throws SQLException {
712 Object parameterObject;
713 TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
714 TypeHandler th = typeHandlerFactory.getTypeHandler(parameterType);
715 parameterObject = th.getResult(rs, mapping.getColumnName());
716 return parameterObject;
717 }
718
719
720
721
722
723
724
725
726
727 private Document newDocument(String root) {
728 try {
729 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
730 doc.appendChild(doc.createElement(root));
731 return doc;
732 } catch (ParserConfigurationException e) {
733 throw new RuntimeException("Error creating XML document. Cause: " + e);
734 }
735 }
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750 private Object prepareDomParameterObject(ResultSet rs, ResultMapping mapping) throws SQLException {
751 TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
752
753 Document doc = newDocument("parameter");
754 Probe probe = ProbeFactory.getProbe(doc);
755
756 String complexName = mapping.getColumnName();
757
758 TypeHandler stringTypeHandler = typeHandlerFactory.getTypeHandler(String.class);
759 if (complexName.indexOf('=') > -1) {
760
761 StringTokenizer parser = new StringTokenizer(complexName, "{}=, ", false);
762 while (parser.hasMoreTokens()) {
763 String propName = parser.nextToken();
764 String colName = parser.nextToken();
765 Object propValue = stringTypeHandler.getResult(rs, colName);
766 probe.setObject(doc, propName, propValue.toString());
767 }
768 } else {
769
770 Object propValue = stringTypeHandler.getResult(rs, complexName);
771 probe.setObject(doc, "value", propValue.toString());
772 }
773
774 return doc;
775 }
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798 private Object prepareBeanParameterObject(StatementScope statementScope, ResultSet rs, ResultMapping mapping,
799 Class parameterType) throws InstantiationException, IllegalAccessException, SQLException {
800 TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
801
802 Object parameterObject;
803 if (parameterType == null) {
804 parameterObject = new HashMap();
805 } else {
806 parameterObject = ResultObjectFactoryUtil.createObjectThroughFactory(parameterType);
807 }
808 String complexName = mapping.getColumnName();
809
810 if (complexName.indexOf('=') > -1 || complexName.indexOf(',') > -1) {
811 StringTokenizer parser = new StringTokenizer(complexName, "{}=, ", false);
812 while (parser.hasMoreTokens()) {
813 String propName = parser.nextToken();
814 String colName = parser.nextToken();
815 Class propType = PROBE.getPropertyTypeForSetter(parameterObject, propName);
816 TypeHandler propTypeHandler = typeHandlerFactory.getTypeHandler(propType);
817 Object propValue = propTypeHandler.getResult(rs, colName);
818 PROBE.setObject(parameterObject, propName, propValue);
819 }
820 } else {
821
822 TypeHandler propTypeHandler = typeHandlerFactory.getTypeHandler(parameterType);
823 if (propTypeHandler == null) {
824 propTypeHandler = typeHandlerFactory.getUnkownTypeHandler();
825 }
826 parameterObject = propTypeHandler.getResult(rs, complexName);
827 }
828
829 return parameterObject;
830 }
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845 protected Object getPrimitiveResultMappingValue(ResultSet rs, ResultMapping mapping) throws SQLException {
846 Object value = null;
847 TypeHandler typeHandler = mapping.getTypeHandler();
848 if (typeHandler != null) {
849 String columnName = mapping.getColumnName();
850 int columnIndex = mapping.getColumnIndex();
851 if (columnName == null) {
852 value = typeHandler.getResult(rs, columnIndex);
853 } else {
854 value = typeHandler.getResult(rs, columnName);
855 }
856 } else {
857 throw new SqlMapException("No type handler could be found to map the property '" + mapping.getPropertyName()
858 + "' to the column '" + mapping.getColumnName()
859 + "'. One or both of the types, or the combination of types is not supported.");
860 }
861 return value;
862 }
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877 protected Object doNullMapping(Object value, ResultMapping mapping) throws SqlMapException {
878 if (value == null) {
879 TypeHandler typeHandler = mapping.getTypeHandler();
880 if (typeHandler != null) {
881 String nullValue = mapping.getNullValue();
882 if (nullValue != null)
883 value = typeHandler.valueOf(nullValue);
884 return value;
885 } else {
886 throw new SqlMapException("No type handler could be found to map the property '" + mapping.getPropertyName()
887 + "' to the column '" + mapping.getColumnName()
888 + "'. One or both of the types, or the combination of types is not supported.");
889 }
890 } else {
891 return value;
892 }
893 }
894 }