001: /*
002: * Copyright 2004 Clinton Begin
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package com.ibatis.sqlmap.engine.mapping.result;
017:
018: import com.ibatis.common.beans.Probe;
019: import com.ibatis.common.beans.ProbeFactory;
020: import com.ibatis.common.jdbc.exception.NestedSQLException;
021:
022: import com.ibatis.sqlmap.client.SqlMapException;
023: import com.ibatis.sqlmap.engine.exchange.DataExchange;
024: import com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient;
025: import com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate;
026: import com.ibatis.sqlmap.engine.mapping.result.loader.ResultLoader;
027: import com.ibatis.sqlmap.engine.mapping.sql.Sql;
028: import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement;
029: import com.ibatis.sqlmap.engine.scope.ErrorContext;
030: import com.ibatis.sqlmap.engine.scope.RequestScope;
031: import com.ibatis.sqlmap.engine.type.DomCollectionTypeMarker;
032: import com.ibatis.sqlmap.engine.type.DomTypeMarker;
033: import com.ibatis.sqlmap.engine.type.TypeHandler;
034: import com.ibatis.sqlmap.engine.type.TypeHandlerFactory;
035: import org.w3c.dom.Document;
036:
037: import javax.xml.parsers.DocumentBuilderFactory;
038: import javax.xml.parsers.ParserConfigurationException;
039: import java.sql.ResultSet;
040: import java.sql.SQLException;
041: import java.util.ArrayList;
042: import java.util.Collection;
043: import java.util.HashMap;
044: import java.util.HashSet;
045: import java.util.Iterator;
046: import java.util.List;
047: import java.util.Map;
048: import java.util.Set;
049: import java.util.StringTokenizer;
050:
051: /**
052: * Basic implementation of ResultMap interface
053: */
054: public class BasicResultMap implements ResultMap {
055:
056: private static final Probe PROBE = ProbeFactory.getProbe();
057: private static final String KEY_SEPARATOR = "\002";
058:
059: private String id;
060: private Class resultClass;
061:
062: // DO NOT ACCESS EITHER OF THESE OUTSIDE OF THEIR BEAN GETTER/SETTER
063: private ResultMapping[] resultMappings;
064: private ThreadLocal remappableResultMappings = new ThreadLocal();
065:
066: private DataExchange dataExchange;
067:
068: private List nestedResultMappings;
069:
070: private Discriminator discriminator;
071:
072: private Set groupByProps;
073:
074: private String xmlName;
075:
076: private String resource;
077:
078: private SqlMapExecutorDelegate delegate;
079:
080: protected boolean allowRemapping = false;
081:
082: /**
083: * Constructor to pass a SqlMapExecutorDelegate in
084: *
085: * @param delegate - the SqlMapExecutorDelegate
086: */
087: public BasicResultMap(SqlMapExecutorDelegate delegate) {
088: this .delegate = delegate;
089: }
090:
091: /**
092: * Getter for the SqlMapExecutorDelegate
093: *
094: * @return - the delegate
095: */
096: public SqlMapExecutorDelegate getDelegate() {
097: return delegate;
098: }
099:
100: public String getId() {
101: return id;
102: }
103:
104: /**
105: * Setter for the ID
106: *
107: * @param id - the new ID
108: */
109: public void setId(String id) {
110: this .id = id;
111: }
112:
113: public Class getResultClass() {
114: return resultClass;
115: }
116:
117: public Object getUniqueKey(String keyPrefix, Object[] values) {
118: if (groupByProps != null) {
119: StringBuffer keyBuffer;
120: if (keyPrefix != null)
121: keyBuffer = new StringBuffer(keyPrefix);
122: else
123: keyBuffer = new StringBuffer();
124: for (int i = 0; i < getResultMappings().length; i++) {
125: String propertyName = getResultMappings()[i]
126: .getPropertyName();
127: if (groupByProps.contains(propertyName)) {
128: keyBuffer.append(values[i]);
129: keyBuffer.append('-');
130: }
131: }
132: if (keyBuffer.length() < 1) {
133: return null;
134: } else {
135: // seperator value not likely to appear in a database
136: keyBuffer.append(KEY_SEPARATOR);
137: return keyBuffer.toString();
138: }
139: } else {
140: return null;
141: }
142: }
143:
144: public Object getUniqueKey(Object[] values) {
145: return getUniqueKey(null, values);
146: }
147:
148: /**
149: * Setter for the result class (what the results will be mapped into)
150: *
151: * @param resultClass - the result class
152: */
153: public void setResultClass(Class resultClass) {
154: this .resultClass = resultClass;
155: }
156:
157: /**
158: * Getter for the DataExchange object to be used
159: *
160: * @return - the DataExchange object
161: */
162: public DataExchange getDataExchange() {
163: return dataExchange;
164: }
165:
166: /**
167: * Setter for the DataExchange object to be used
168: *
169: * @param dataExchange - the new DataExchange object
170: */
171: public void setDataExchange(DataExchange dataExchange) {
172: this .dataExchange = dataExchange;
173: }
174:
175: /**
176: * Getter (used by DomDataExchange) for the xml name of the results
177: *
178: * @return - the name
179: */
180: public String getXmlName() {
181: return xmlName;
182: }
183:
184: /**
185: * Setter (used by the SqlMapBuilder) for the xml name of the results
186: *
187: * @param xmlName - the name
188: */
189: public void setXmlName(String xmlName) {
190: this .xmlName = xmlName;
191: }
192:
193: /**
194: * Getter for the resource (used to report errors)
195: *
196: * @return - the resource
197: */
198: public String getResource() {
199: return resource;
200: }
201:
202: /**
203: * Setter for the resource (used by the SqlMapBuilder)
204: *
205: * @param resource - the resource name
206: */
207: public void setResource(String resource) {
208: this .resource = resource;
209: }
210:
211: public void addGroupByProperty(String name) {
212: if (groupByProps == null) {
213: groupByProps = new HashSet();
214: }
215: groupByProps.add(name);
216: }
217:
218: public boolean hasGroupBy() {
219: return groupByProps != null && groupByProps.size() > 0;
220: }
221:
222: public Iterator groupByProps() {
223: return groupByProps.iterator();
224: }
225:
226: public void addNestedResultMappings(ResultMapping mapping) {
227: if (nestedResultMappings == null) {
228: nestedResultMappings = new ArrayList();
229: }
230: nestedResultMappings.add(mapping);
231: }
232:
233: public List getNestedResultMappings() {
234: return nestedResultMappings;
235: }
236:
237: public ResultMapping[] getResultMappings() {
238: if (allowRemapping) {
239: return (ResultMapping[]) remappableResultMappings.get();
240: } else {
241: return resultMappings;
242: }
243: }
244:
245: public void setDiscriminator(Discriminator discriminator) {
246: if (this .discriminator != null) {
247: throw new SqlMapException(
248: "A discriminator may only be set once per result map.");
249: }
250: this .discriminator = discriminator;
251: }
252:
253: public Discriminator getDiscriminator() {
254: return discriminator;
255: }
256:
257: public ResultMap resolveSubMap(RequestScope request, ResultSet rs)
258: throws SQLException {
259: ResultMap subMap = this ;
260: if (discriminator != null) {
261: BasicResultMapping mapping = (BasicResultMapping) discriminator
262: .getResultMapping();
263: Object value = getPrimitiveResultMappingValue(rs, mapping);
264: if (value == null) {
265: value = doNullMapping(value, mapping);
266: }
267: subMap = discriminator.getSubMap(String.valueOf(value));
268: if (subMap == null) {
269: subMap = this ;
270: } else if (subMap != this ) {
271: subMap = subMap.resolveSubMap(request, rs);
272: }
273: }
274: return subMap;
275: }
276:
277: /**
278: * Setter for a list of the individual ResultMapping objects
279: *
280: * @param resultMappingList - the list
281: */
282: public void setResultMappingList(List resultMappingList) {
283: if (allowRemapping) {
284: this .remappableResultMappings
285: .set((BasicResultMapping[]) resultMappingList
286: .toArray(new BasicResultMapping[resultMappingList
287: .size()]));
288: } else {
289: this .resultMappings = (BasicResultMapping[]) resultMappingList
290: .toArray(new BasicResultMapping[resultMappingList
291: .size()]);
292: }
293:
294: Map props = new HashMap();
295: props.put("map", this );
296: dataExchange = getDelegate().getDataExchangeFactory()
297: .getDataExchangeForClass(resultClass);
298: dataExchange.initialize(props);
299: }
300:
301: /**
302: * Getter for the number of ResultMapping objects
303: *
304: * @return - the count
305: */
306: public int getResultCount() {
307: return this .getResultMappings().length;
308: }
309:
310: /**
311: * Read a row from a resultset and map results to an array.
312: *
313: * @param request scope of the request
314: * @param rs ResultSet to read from
315: *
316: * @return row read as an array of column values.
317: *
318: * @throws java.sql.SQLException
319: */
320: public Object[] getResults(RequestScope request, ResultSet rs)
321: throws SQLException {
322: ErrorContext errorContext = request.getErrorContext();
323: errorContext.setActivity("applying a result map");
324: errorContext.setObjectId(this .getId());
325: errorContext.setResource(this .getResource());
326: errorContext.setMoreInfo("Check the result map.");
327:
328: boolean foundData = false;
329: Object[] columnValues = new Object[getResultMappings().length];
330: for (int i = 0; i < getResultMappings().length; i++) {
331: BasicResultMapping mapping = (BasicResultMapping) getResultMappings()[i];
332: errorContext.setMoreInfo(mapping.getErrorString());
333: if (mapping.getStatementName() != null) {
334: if (resultClass == null) {
335: throw new SqlMapException(
336: "The result class was null when trying to get results for ResultMap named "
337: + getId() + ".");
338: } else if (Map.class.isAssignableFrom(resultClass)) {
339: Class javaType = mapping.getJavaType();
340: if (javaType == null) {
341: javaType = Object.class;
342: }
343: columnValues[i] = getNestedSelectMappingValue(
344: request, rs, mapping, javaType);
345: } else if (DomTypeMarker.class
346: .isAssignableFrom(resultClass)) {
347: Class javaType = mapping.getJavaType();
348: if (javaType == null) {
349: javaType = DomTypeMarker.class;
350: }
351: columnValues[i] = getNestedSelectMappingValue(
352: request, rs, mapping, javaType);
353: } else {
354: Probe p = ProbeFactory.getProbe(resultClass);
355: Class type = p.getPropertyTypeForSetter(
356: resultClass, mapping.getPropertyName());
357: columnValues[i] = getNestedSelectMappingValue(
358: request, rs, mapping, type);
359: }
360: foundData = foundData || columnValues[i] != null;
361: } else if (mapping.getNestedResultMapName() == null) {
362: columnValues[i] = getPrimitiveResultMappingValue(rs,
363: mapping);
364: if (columnValues[i] == null) {
365: columnValues[i] = doNullMapping(columnValues[i],
366: mapping);
367: } else {
368: foundData = true;
369: }
370: }
371: }
372:
373: request.setRowDataFound(foundData);
374:
375: return columnValues;
376: }
377:
378: public Object setResultObjectValues(RequestScope request,
379: Object resultObject, Object[] values) {
380:
381: String ukey = (String) getUniqueKey(request
382: .getCurrentNestedKey(), values);
383:
384: Map uniqueKeys = request.getUniqueKeys(this );
385:
386: request.setCurrentNestedKey(ukey);
387: if (uniqueKeys != null && uniqueKeys.containsKey(ukey)) {
388: // Unique key is already known, so get the existing result object and process additional results.
389: resultObject = uniqueKeys.get(ukey);
390: applyNestedResultMap(request, resultObject, values);
391: resultObject = NO_VALUE;
392: } else if (ukey == null || uniqueKeys == null
393: || !uniqueKeys.containsKey(ukey)) {
394: // Unique key is NOT known, so create a new result object and then process additional results.
395: resultObject = dataExchange.setData(request, this ,
396: resultObject, values);
397: // Lazy init key set, only if we're grouped by something (i.e. ukey != null)
398: if (ukey != null) {
399: if (uniqueKeys == null) {
400: uniqueKeys = new HashMap();
401: request.setUniqueKeys(this , uniqueKeys);
402: }
403: uniqueKeys.put(ukey, resultObject);
404: }
405: applyNestedResultMap(request, resultObject, values);
406: } else {
407: // Otherwise, we don't care about these results.
408: resultObject = NO_VALUE;
409: }
410:
411: return resultObject;
412: }
413:
414: private void applyNestedResultMap(RequestScope request,
415: Object resultObject, Object[] values) {
416: if (resultObject != null && resultObject != NO_VALUE) {
417: if (nestedResultMappings != null) {
418: for (int i = 0, n = nestedResultMappings.size(); i < n; i++) {
419: BasicResultMapping resultMapping = (BasicResultMapping) nestedResultMappings
420: .get(i);
421: setNestedResultMappingValue(resultMapping, request,
422: resultObject, values);
423: }
424: }
425: }
426: }
427:
428: /**
429: * Some changes in this method for IBATIS-225:
430: * <ul>
431: * <li>We no longer require the nested property to be a collection. This
432: * will allow reuses of resultMaps on 1:1 relationships</li>
433: * <li>If the nested property is not a collection, then it will be
434: * created/replaced by the values generated from the current row.</li>
435: * </ul>
436: *
437: * @param mapping
438: * @param request
439: * @param resultObject
440: * @param values
441: */
442: protected void setNestedResultMappingValue(
443: BasicResultMapping mapping, RequestScope request,
444: Object resultObject, Object[] values) {
445: try {
446:
447: String resultMapName = mapping.getNestedResultMapName();
448: ResultMap resultMap = getDelegate().getResultMap(
449: resultMapName);
450: // get the discriminated submap if it exists
451: resultMap = resultMap.resolveSubMap(request, request
452: .getResultSet());
453:
454: Class type = mapping.getJavaType();
455: String propertyName = mapping.getPropertyName();
456:
457: Object obj = PROBE.getObject(resultObject, propertyName);
458:
459: if (obj == null) {
460: if (type == null) {
461: type = PROBE.getPropertyTypeForSetter(resultObject,
462: propertyName);
463: }
464:
465: try {
466: // create the object if is it a Collection. If not a Collection
467: // then we will just set the property to the object created
468: // in processing the nested result map
469: if (Collection.class.isAssignableFrom(type)) {
470: obj = ResultObjectFactoryUtil
471: .createObjectThroughFactory(type);
472: PROBE
473: .setObject(resultObject, propertyName,
474: obj);
475: }
476: } catch (Exception e) {
477: throw new SqlMapException(
478: "Error instantiating collection property for mapping '"
479: + mapping.getPropertyName()
480: + "'. Cause: " + e, e);
481: }
482: }
483:
484: values = resultMap.getResults(request, request
485: .getResultSet());
486: if (request.isRowDataFound()) {
487: Object o = resultMap.setResultObjectValues(request,
488: null, values);
489: if (o != NO_VALUE) {
490: if (obj != null && obj instanceof Collection) {
491: ((Collection) obj).add(o);
492: } else {
493: PROBE.setObject(resultObject, propertyName, o);
494: }
495: }
496: }
497: } catch (SQLException e) {
498: throw new SqlMapException(
499: "Error getting nested result map values for '"
500: + mapping.getPropertyName() + "'. Cause: "
501: + e, e);
502: }
503: }
504:
505: protected Object getNestedSelectMappingValue(RequestScope request,
506: ResultSet rs, BasicResultMapping mapping, Class targetType)
507: throws SQLException {
508: try {
509: TypeHandlerFactory typeHandlerFactory = getDelegate()
510: .getTypeHandlerFactory();
511:
512: String statementName = mapping.getStatementName();
513: ExtendedSqlMapClient client = (ExtendedSqlMapClient) request
514: .getSession().getSqlMapClient();
515:
516: MappedStatement mappedStatement = client
517: .getMappedStatement(statementName);
518: Class parameterType = mappedStatement.getParameterClass();
519: Object parameterObject = null;
520:
521: if (parameterType == null) {
522: parameterObject = prepareBeanParameterObject(request,
523: rs, mapping, parameterType);
524: } else {
525: if (typeHandlerFactory.hasTypeHandler(parameterType)) {
526: parameterObject = preparePrimitiveParameterObject(
527: rs, mapping, parameterType);
528: } else if (DomTypeMarker.class
529: .isAssignableFrom(parameterType)) {
530: parameterObject = prepareDomParameterObject(rs,
531: mapping);
532: } else {
533: parameterObject = prepareBeanParameterObject(
534: request, rs, mapping, parameterType);
535: }
536: }
537:
538: Object result = null;
539: if (parameterObject != null) {
540:
541: Sql sql = mappedStatement.getSql();
542: ResultMap resultMap = sql.getResultMap(request,
543: parameterObject);
544: Class resultClass = resultMap.getResultClass();
545:
546: if (resultClass != null
547: && !DomTypeMarker.class
548: .isAssignableFrom(targetType)) {
549: if (DomCollectionTypeMarker.class
550: .isAssignableFrom(resultClass)) {
551: targetType = DomCollectionTypeMarker.class;
552: } else if (DomTypeMarker.class
553: .isAssignableFrom(resultClass)) {
554: targetType = DomTypeMarker.class;
555: }
556: }
557:
558: result = ResultLoader.loadResult(client, statementName,
559: parameterObject, targetType);
560:
561: String nullValue = mapping.getNullValue();
562: if (result == null && nullValue != null) {
563: TypeHandler typeHandler = typeHandlerFactory
564: .getTypeHandler(targetType);
565: if (typeHandler != null) {
566: result = typeHandler.valueOf(nullValue);
567: }
568: }
569: }
570: return result;
571: } catch (InstantiationException e) {
572: throw new NestedSQLException(
573: "Error setting nested bean property. Cause: " + e,
574: e);
575: } catch (IllegalAccessException e) {
576: throw new NestedSQLException(
577: "Error setting nested bean property. Cause: " + e,
578: e);
579: }
580:
581: }
582:
583: private Object preparePrimitiveParameterObject(ResultSet rs,
584: BasicResultMapping mapping, Class parameterType)
585: throws SQLException {
586: Object parameterObject;
587: TypeHandlerFactory typeHandlerFactory = getDelegate()
588: .getTypeHandlerFactory();
589: TypeHandler th = typeHandlerFactory
590: .getTypeHandler(parameterType);
591: parameterObject = th.getResult(rs, mapping.getColumnName());
592: return parameterObject;
593: }
594:
595: private Document newDocument(String root) {
596: try {
597: Document doc = DocumentBuilderFactory.newInstance()
598: .newDocumentBuilder().newDocument();
599: doc.appendChild(doc.createElement(root));
600: return doc;
601: } catch (ParserConfigurationException e) {
602: throw new RuntimeException(
603: "Error creating XML document. Cause: " + e);
604: }
605: }
606:
607: private Object prepareDomParameterObject(ResultSet rs,
608: BasicResultMapping mapping) throws SQLException {
609: TypeHandlerFactory typeHandlerFactory = getDelegate()
610: .getTypeHandlerFactory();
611:
612: Document doc = newDocument("parameter");
613: Probe probe = ProbeFactory.getProbe(doc);
614:
615: String complexName = mapping.getColumnName();
616:
617: TypeHandler stringTypeHandler = typeHandlerFactory
618: .getTypeHandler(String.class);
619: if (complexName.indexOf('=') > -1) {
620: // old 1.x style multiple params
621: StringTokenizer parser = new StringTokenizer(complexName,
622: "{}=, ", false);
623: while (parser.hasMoreTokens()) {
624: String propName = parser.nextToken();
625: String colName = parser.nextToken();
626: Object propValue = stringTypeHandler.getResult(rs,
627: colName);
628: probe.setObject(doc, propName, propValue.toString());
629: }
630: } else {
631: // single param
632: Object propValue = stringTypeHandler.getResult(rs,
633: complexName);
634: probe.setObject(doc, "value", propValue.toString());
635: }
636:
637: return doc;
638: }
639:
640: private Object prepareBeanParameterObject(RequestScope request,
641: ResultSet rs, BasicResultMapping mapping,
642: Class parameterType) throws InstantiationException,
643: IllegalAccessException, SQLException {
644: TypeHandlerFactory typeHandlerFactory = getDelegate()
645: .getTypeHandlerFactory();
646:
647: Object parameterObject;
648: if (parameterType == null) {
649: parameterObject = new HashMap();
650: } else {
651: parameterObject = ResultObjectFactoryUtil
652: .createObjectThroughFactory(parameterType);
653: }
654: String complexName = mapping.getColumnName();
655:
656: if (complexName.indexOf('=') > -1
657: || complexName.indexOf(',') > -1) {
658: StringTokenizer parser = new StringTokenizer(complexName,
659: "{}=, ", false);
660: while (parser.hasMoreTokens()) {
661: String propName = parser.nextToken();
662: String colName = parser.nextToken();
663: Class propType = PROBE.getPropertyTypeForSetter(
664: parameterObject, propName);
665: TypeHandler propTypeHandler = typeHandlerFactory
666: .getTypeHandler(propType);
667: Object propValue = propTypeHandler.getResult(rs,
668: colName);
669: PROBE.setObject(parameterObject, propName, propValue);
670: }
671: } else {
672: // single param
673: TypeHandler propTypeHandler = typeHandlerFactory
674: .getTypeHandler(parameterType);
675: if (propTypeHandler == null) {
676: propTypeHandler = typeHandlerFactory
677: .getUnkownTypeHandler();
678: }
679: parameterObject = propTypeHandler
680: .getResult(rs, complexName);
681: }
682:
683: return parameterObject;
684: }
685:
686: protected Object getPrimitiveResultMappingValue(ResultSet rs,
687: BasicResultMapping mapping) throws SQLException {
688: Object value = null;
689: TypeHandler typeHandler = mapping.getTypeHandler();
690: if (typeHandler != null) {
691: String columnName = mapping.getColumnName();
692: int columnIndex = mapping.getColumnIndex();
693: if (columnName == null) {
694: value = typeHandler.getResult(rs, columnIndex);
695: } else {
696: value = typeHandler.getResult(rs, columnName);
697: }
698: } else {
699: throw new SqlMapException(
700: "No type handler could be found to map the property '"
701: + mapping.getPropertyName()
702: + "' to the column '"
703: + mapping.getColumnName()
704: + "'. One or both of the types, or the combination of types is not supported.");
705: }
706: return value;
707: }
708:
709: protected Object doNullMapping(Object value,
710: BasicResultMapping mapping) throws SqlMapException {
711: if (value == null) {
712: TypeHandler typeHandler = mapping.getTypeHandler();
713: if (typeHandler != null) {
714: String nullValue = mapping.getNullValue();
715: if (nullValue != null)
716: value = typeHandler.valueOf(nullValue);
717: return value;
718: } else {
719: throw new SqlMapException(
720: "No type handler could be found to map the property '"
721: + mapping.getPropertyName()
722: + "' to the column '"
723: + mapping.getColumnName()
724: + "'. One or both of the types, or the combination of types is not supported.");
725: }
726: } else {
727: return value;
728: }
729: }
730: }
|