View Javadoc
1   /*
2    * Copyright 2004-2023 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  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   * Basic implementation of ResultMap interface.
54   */
55  public class ResultMap {
56  
57    /** The Constant PROBE. */
58    private static final Probe PROBE = ProbeFactory.getProbe();
59  
60    /** The Constant KEY_SEPARATOR. */
61    private static final String KEY_SEPARATOR = "\002";
62  
63    /** The id. */
64    private String id;
65  
66    /** The result class. */
67    private Class resultClass;
68  
69    /** The result mappings. */
70    // DO NOT ACCESS EITHER OF THESE OUTSIDE OF THEIR BEAN GETTER/SETTER
71    private ResultMapping[] resultMappings;
72  
73    /** The remappable result mappings. */
74    private ThreadLocal remappableResultMappings = new ThreadLocal();
75  
76    /** The data exchange. */
77    private DataExchange dataExchange;
78  
79    /** The nested result mappings. */
80    private List nestedResultMappings;
81  
82    /** The discriminator. */
83    private Discriminator discriminator;
84  
85    /** The group by props. */
86    private Set groupByProps;
87  
88    /** The xml name. */
89    private String xmlName;
90  
91    /** The resource. */
92    private String resource;
93  
94    /** The delegate. */
95    protected SqlMapExecutorDelegate delegate;
96  
97    /** The allow remapping. */
98    protected boolean allowRemapping = false;
99  
100   /** The Constant NO_VALUE. */
101   public static final Object NO_VALUE = new Object();
102 
103   /**
104    * Constructor to pass a SqlMapExecutorDelegate in.
105    *
106    * @param delegate
107    *          - the SqlMapExecutorDelegate
108    */
109   public ResultMap(SqlMapExecutorDelegate delegate) {
110     this.delegate = delegate;
111   }
112 
113   /**
114    * Getter for the SqlMapExecutorDelegate.
115    *
116    * @return - the delegate
117    */
118   public SqlMapExecutorDelegate getDelegate() {
119     return delegate;
120   }
121 
122   /**
123    * Gets the id.
124    *
125    * @return the id
126    */
127   public String getId() {
128     return id;
129   }
130 
131   /**
132    * Setter for the ID.
133    *
134    * @param id
135    *          - the new ID
136    */
137   public void setId(String id) {
138     this.id = id;
139   }
140 
141   /**
142    * Gets the result class.
143    *
144    * @return the result class
145    */
146   public Class getResultClass() {
147     return resultClass;
148   }
149 
150   /**
151    * Gets the unique key.
152    *
153    * @param keyPrefix
154    *          the key prefix
155    * @param values
156    *          the values
157    *
158    * @return the unique key
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         // seperator value not likely to appear in a database
178         keyBuffer.append(KEY_SEPARATOR);
179         return keyBuffer.toString();
180       }
181     } else {
182       return null;
183     }
184   }
185 
186   /**
187    * Gets the unique key.
188    *
189    * @param values
190    *          the values
191    *
192    * @return the unique key
193    */
194   public Object getUniqueKey(Object[] values) {
195     return getUniqueKey(null, values);
196   }
197 
198   /**
199    * Setter for the result class (what the results will be mapped into).
200    *
201    * @param resultClass
202    *          - the result class
203    */
204   public void setResultClass(Class resultClass) {
205     this.resultClass = resultClass;
206   }
207 
208   /**
209    * Getter for the DataExchange object to be used.
210    *
211    * @return - the DataExchange object
212    */
213   public DataExchange getDataExchange() {
214     return dataExchange;
215   }
216 
217   /**
218    * Setter for the DataExchange object to be used.
219    *
220    * @param dataExchange
221    *          - the new DataExchange object
222    */
223   public void setDataExchange(DataExchange dataExchange) {
224     this.dataExchange = dataExchange;
225   }
226 
227   /**
228    * Getter (used by DomDataExchange) for the xml name of the results.
229    *
230    * @return - the name
231    */
232   public String getXmlName() {
233     return xmlName;
234   }
235 
236   /**
237    * Setter (used by the SqlMapBuilder) for the xml name of the results.
238    *
239    * @param xmlName
240    *          - the name
241    */
242   public void setXmlName(String xmlName) {
243     this.xmlName = xmlName;
244   }
245 
246   /**
247    * Getter for the resource (used to report errors).
248    *
249    * @return - the resource
250    */
251   public String getResource() {
252     return resource;
253   }
254 
255   /**
256    * Setter for the resource (used by the SqlMapBuilder).
257    *
258    * @param resource
259    *          - the resource name
260    */
261   public void setResource(String resource) {
262     this.resource = resource;
263   }
264 
265   /**
266    * Adds the group by property.
267    *
268    * @param name
269    *          the name
270    */
271   public void addGroupByProperty(String name) {
272     if (groupByProps == null) {
273       groupByProps = new HashSet();
274     }
275     groupByProps.add(name);
276   }
277 
278   /**
279    * Checks for group by.
280    *
281    * @return true, if successful
282    */
283   public boolean hasGroupBy() {
284     return groupByProps != null && groupByProps.size() > 0;
285   }
286 
287   /**
288    * Group by props.
289    *
290    * @return the iterator
291    */
292   public Iterator groupByProps() {
293     return groupByProps.iterator();
294   }
295 
296   /**
297    * Adds the nested result mappings.
298    *
299    * @param mapping
300    *          the mapping
301    */
302   public void addNestedResultMappings(ResultMapping mapping) {
303     if (nestedResultMappings == null) {
304       nestedResultMappings = new ArrayList();
305     }
306     nestedResultMappings.add(mapping);
307   }
308 
309   /**
310    * Gets the nested result mappings.
311    *
312    * @return the nested result mappings
313    */
314   public List getNestedResultMappings() {
315     return nestedResultMappings;
316   }
317 
318   /**
319    * Gets the result mappings.
320    *
321    * @return the result mappings
322    */
323   public ResultMapping[] getResultMappings() {
324     if (allowRemapping) {
325       return (ResultMapping[]) remappableResultMappings.get();
326     } else {
327       return resultMappings;
328     }
329   }
330 
331   /**
332    * Sets the discriminator.
333    *
334    * @param discriminator
335    *          the new discriminator
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    * Gets the discriminator.
346    *
347    * @return the discriminator
348    */
349   public Discriminator getDiscriminator() {
350     return discriminator;
351   }
352 
353   /**
354    * Resolve sub map.
355    *
356    * @param statementScope
357    *          the statement scope
358    * @param rs
359    *          the rs
360    *
361    * @return the result map
362    *
363    * @throws SQLException
364    *           the SQL exception
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    * Setter for a list of the individual ResultMapping objects.
386    *
387    * @param resultMappingList
388    *          - the list
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    * Getter for the number of ResultMapping objects.
406    *
407    * @return - the count
408    */
409   public int getResultCount() {
410     return this.getResultMappings().length;
411   }
412 
413   /**
414    * Read a row from a resultset and map results to an array.
415    *
416    * @param statementScope
417    *          scope of the request
418    * @param rs
419    *          ResultSet to read from
420    *
421    * @return row read as an array of column values.
422    *
423    * @throws SQLException
424    *           the SQL exception
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    * Sets the result object values.
477    *
478    * @param statementScope
479    *          the statement scope
480    * @param resultObject
481    *          the result object
482    * @param values
483    *          the values
484    *
485    * @return the object
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       // Unique key is already known, so get the existing result object and process additional
494       // results.
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       // Unique key is NOT known, so create a new result object and then process additional
500       // results.
501       resultObject = dataExchange.setData(statementScope, this, resultObject, values);
502       // Lazy init key set, only if we're grouped by something (i.e. ukey != null)
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       // Otherwise, we don't care about these results.
513       resultObject = NO_VALUE;
514     }
515 
516     statementScope.setCurrentNestedKey(previousNestedKey);
517     return resultObject;
518   }
519 
520   /**
521    * Apply nested result map.
522    *
523    * @param statementScope
524    *          the statement scope
525    * @param resultObject
526    *          the result object
527    * @param values
528    *          the values
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    * Some changes in this method for IBATIS-225:
543    * <ul>
544    * <li>We no longer require the nested property to be a collection. This will allow reuses of resultMaps on 1:1
545    * relationships</li>
546    * <li>If the nested property is not a collection, then it will be created/replaced by the values generated from the
547    * current row.</li>
548    * </ul>
549    *
550    * @param mapping
551    *          the mapping
552    * @param statementScope
553    *          the statement scope
554    * @param resultObject
555    *          the result object
556    * @param values
557    *          the values
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       // get the discriminated submap if it exists
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           // create the object if is it a Collection. If not a Collection
580           // then we will just set the property to the object created
581           // in processing the nested result map
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       // JIRA 375
594       // "Provide a way for not creating items from nested ResultMaps when the items contain only null values"
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    * Gets the nested select mapping value.
622    *
623    * @param statementScope
624    *          the statement scope
625    * @param rs
626    *          the rs
627    * @param mapping
628    *          the mapping
629    * @param targetType
630    *          the target type
631    *
632    * @return the nested select mapping value
633    *
634    * @throws SQLException
635    *           the SQL exception
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    * Prepare primitive parameter object.
697    *
698    * @param rs
699    *          the rs
700    * @param mapping
701    *          the mapping
702    * @param parameterType
703    *          the parameter type
704    *
705    * @return the object
706    *
707    * @throws SQLException
708    *           the SQL exception
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    * New document.
721    *
722    * @param root
723    *          the root
724    *
725    * @return the document
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    * Prepare dom parameter object.
739    *
740    * @param rs
741    *          the rs
742    * @param mapping
743    *          the mapping
744    *
745    * @return the object
746    *
747    * @throws SQLException
748    *           the SQL exception
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       // old 1.x style multiple params
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       // single param
770       Object propValue = stringTypeHandler.getResult(rs, complexName);
771       probe.setObject(doc, "value", propValue.toString());
772     }
773 
774     return doc;
775   }
776 
777   /**
778    * Prepare bean parameter object.
779    *
780    * @param statementScope
781    *          the statement scope
782    * @param rs
783    *          the rs
784    * @param mapping
785    *          the mapping
786    * @param parameterType
787    *          the parameter type
788    *
789    * @return the object
790    *
791    * @throws InstantiationException
792    *           the instantiation exception
793    * @throws IllegalAccessException
794    *           the illegal access exception
795    * @throws SQLException
796    *           the SQL exception
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       // single param
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    * Gets the primitive result mapping value.
834    *
835    * @param rs
836    *          the rs
837    * @param mapping
838    *          the mapping
839    *
840    * @return the primitive result mapping value
841    *
842    * @throws SQLException
843    *           the SQL exception
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    * Do null mapping.
866    *
867    * @param value
868    *          the value
869    * @param mapping
870    *          the mapping
871    *
872    * @return the object
873    *
874    * @throws SqlMapException
875    *           the sql map exception
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 }