Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java?rev=683745&view=auto ============================================================================== --- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java (added) +++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java Thu Aug 7 16:21:46 2008 @@ -0,0 +1,472 @@ +package org.apache.ibatis.executor.resultset; + +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.mapping.Configuration; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.loader.*; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.executor.result.*; +import org.apache.ibatis.mapping.*; +import org.apache.ibatis.reflection.*; +import org.apache.ibatis.type.*; + +import java.sql.*; +import java.util.*; + +public class DefaultResultSetHandler implements ResultSetHandler { + + private static final Object NO_VALUE = new Object(); + + private final Executor executor; + private final ObjectFactory objectFactory; + private final TypeHandlerRegistry typeHandlerRegistry; + private final MappedStatement mappedStatement; + private final int rowOffset; + private final int rowLimit; + private final Object parameterObject; + + private final Map nestedResultObjects; + private CacheKey currentNestedKey; + + private ResultHandler resultHandler; + + private Reference<Boolean> foundValues; + + public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, int rowOffset, int rowLimit, ResultHandler resultHandler) { + this.executor = executor; + this.objectFactory = mappedStatement.getConfiguration().getObjectFactory(); + this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry(); + this.mappedStatement = mappedStatement; + this.rowOffset = rowOffset; + this.rowLimit = rowLimit; + this.parameterObject = parameterHandler.getParameterObject(); + this.nestedResultObjects = new HashMap(); + this.resultHandler = resultHandler; + this.foundValues = new Reference<Boolean>(false); + } + + public List handleResultSets(Statement statement) throws SQLException { + List<List> resultsList = new ArrayList<List>(); + ResultSet rs = getFirstResultSet(statement); + if (rs != null) { + try { + for (int i = 0, n = mappedStatement.getResultMaps().size(); i < n; i++) { + ResultMap resultMap = mappedStatement.getResultMaps().get(i); + if (resultHandler == null) { + DefaultResultHandler defaultResultHandler = new DefaultResultHandler(); + handleResults(rs, resultMap, defaultResultHandler, rowOffset, rowLimit); + resultsList.add(defaultResultHandler.getResultList()); + } else { + handleResults(rs, resultMap, resultHandler, rowOffset, rowLimit); + } + if (moveToNextResultsSafely(statement)) { + rs = statement.getResultSet(); + nestedResultObjects.clear(); + } else { + break; + } + } + } finally { + closeResultSet(rs); + } + } + if (resultsList.size() == 1) { + return resultsList.get(0); + } else { + return resultsList; + } + } + + public void handleOutputParameters(CallableStatement callableStatement) throws SQLException { + ParameterMap parameterMap = mappedStatement.getParameterMap(); + MetaObject metaParam = MetaObject.forObject(parameterObject); + List<ParameterMapping> parameterMappings = mappedStatement.getDynamicParameterMappings(parameterObject); + for (int i = 0; i < parameterMappings.size(); i++) { + ParameterMapping parameterMapping = parameterMappings.get(i); + if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) { + if ("java.sql.ResultSet".equalsIgnoreCase(parameterMapping.getJavaType().getName())) { + // TODO: We need an easy way to unit test this without installing Oracle. + // Mocks are obvious, but will they be effective enough? DBunit? + ResultSet rs = (ResultSet) callableStatement.getObject(i + 1); + ResultMap resultMap = mappedStatement.getConfiguration().getResultMap(parameterMapping.getResultMapId()); + if (resultMap == null) { + throw new RuntimeException("Parameter requires ResultMap for output types of java.sql.ResultSet"); + } else { + DefaultResultHandler resultHandler = new DefaultResultHandler(); + handleResults(rs, resultMap, resultHandler, Executor.NO_ROW_OFFSET, Executor.NO_ROW_LIMIT); + metaParam.setValue(parameterMapping.getProperty(), resultHandler.getResultList()); + } + rs.close(); + } else { + metaParam.setValue(parameterMapping.getProperty(), parameterMapping.getTypeHandler().getResult(callableStatement, i + 1)); + } + } + } + } + + private void handleResults(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, int skipResults, int maxResults) throws SQLException { + if (resultMap != null) { + skipResults(rs, skipResults); + int resultsFetched = 0; + while ((maxResults == Executor.NO_ROW_LIMIT || resultsFetched < maxResults) && rs.next()) { + currentNestedKey = null; + ResultMap rm = resolveSubMap(rs, resultMap); + Object resultObject = loadResultObject(rs, rm); + if (resultObject != NO_VALUE) { + if (resultObject instanceof PlatformTypeHolder) { + resultObject = ((PlatformTypeHolder) resultObject).get(null); + } + resultHandler.handleResult(resultObject); + } + resultsFetched++; + } + } + } + + private Object loadResultObject(ResultSet rs, ResultMap rm) throws SQLException { + if (rm.getType() == null) { + throw new RuntimeException("The result class was null when trying to get results for ResultMap."); + } + + Object resultObject = createResultObject(rs, rm); + ResultLoaderRegistry lazyLoader = null; + if (this.mappedStatement.getConfiguration().isEnhancementEnabled()) { + lazyLoader = new ResultLoaderRegistry(); + resultObject = ResultObjectProxy.createProxy(rm.getType(), resultObject, lazyLoader); + } + + // Rethink this implementation of foundValues. It's the only volatile state on the class... + foundValues = new Reference<Boolean>(false); + List<ResultMapping> appliedResultMappings = new ArrayList<ResultMapping>(); + resultObject = mapResults(rs, rm, lazyLoader, resultObject, appliedResultMappings); + resultObject = processNestedJoinResults(rs, appliedResultMappings, resultObject); + return resultObject; + } + + private Object mapResults(ResultSet rs, ResultMap rm, ResultLoaderRegistry lazyLoader, Object resultObject, List<ResultMapping> appliedResultMappings) throws SQLException { + MetaObject metaResultObject = MetaObject.forObject(resultObject); + Set<String> propSet = new HashSet<String>(); + Set<String> colSet = new HashSet<String>(); + Map<String, ResultMapping> autoMappings = new HashMap<String, ResultMapping>(); + ResultSetMetaData rsmd = rs.getMetaData(); + for (int i = 1, n = rsmd.getColumnCount(); i <= n; i++) { + boolean useLabel = mappedStatement.getConfiguration().isUseColumnLabel(); + String columnLabel = (useLabel ? rsmd.getColumnLabel(i) : rsmd.getColumnName(i)); + columnLabel.toUpperCase(); + String propName = metaResultObject.findProperty(columnLabel); + colSet.add(columnLabel); + if (propName != null) { + propSet.add(propName); + Class javaType = metaResultObject.getSetterType(propName); + TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(javaType); + ResultMapping resultMapping = new ResultMapping.Builder(propName, columnLabel, typeHandler) + .javaType(javaType).build(); + autoMappings.put(propName, resultMapping); + } + } + // Map results/ignore missing + for (ResultMapping resultMapping : rm.getPropertyResultMappings()) { + String propName = resultMapping.getProperty(); + String colName = resultMapping.getColumn(); + colName = colName == null ? null : colName.toUpperCase(); + autoMappings.remove(propName); + if (colName == null || colSet.contains(colName)) { + resultObject = processResult(rs, rm, resultMapping, lazyLoader, resultObject); + appliedResultMappings.add(resultMapping); + } + } + // Automap remaining results + for (String key : autoMappings.keySet()) { + ResultMapping autoMapping = autoMappings.get(key); + resultObject = processResult(rs, rm, autoMapping, lazyLoader, resultObject); + appliedResultMappings.add(autoMapping); + } + return resultObject; + } + + private Object createResultObject(ResultSet rs, ResultMap rm) throws SQLException { + if (PlatformTypeHolder.isPlatformType(rm.getType())) { + return new PlatformTypeHolder(); + } + Object resultObject; + if (rm.getConstructorResultMappings().size() > 0) { + Map<String, Object> constructorArgs = new HashMap<String, Object>(); + List<Class> argTypes = new ArrayList<Class>(); + List<Object> argValues = new ArrayList<Object>(); + for (ResultMapping resultMapping : rm.getConstructorResultMappings()) { + processResult(rs, rm, resultMapping, null, constructorArgs); + argTypes.add(resultMapping.getJavaType()); + argValues.add(constructorArgs.get(resultMapping.getProperty())); + } + resultObject = objectFactory.create(rm.getType(), argTypes, argValues); + } else { + resultObject = objectFactory.create(rm.getType()); + } + return resultObject; + } + + private Object processNestedSelectResult(MappedStatement nestedQuery, ResultMap rm, ResultMapping resultMapping, ResultLoaderRegistry lazyLoader, Object parameterObject, Object resultObject) { + MetaObject metaResultObject = MetaObject.forObject(resultObject); + Object value = null; + try { + if (parameterObject != null) { + CacheKey key = executor.createCacheKey(nestedQuery, parameterObject, Executor.NO_ROW_OFFSET, Executor.NO_ROW_LIMIT); + if (executor.isCached(nestedQuery, key)) { + executor.deferLoad(nestedQuery, metaResultObject, resultMapping.getProperty(), key); + } else { + ResultLoader resultLoader = new ResultLoader(executor, nestedQuery, parameterObject, resultMapping.getJavaType()); + if (lazyLoader == null) { + value = resultLoader.loadResult(); + } else { + lazyLoader.registerLoader(resultMapping.getProperty(), metaResultObject, resultLoader); + } + } + } + } catch (Exception e) { + throw new RuntimeException("Error setting nested bean property. Cause: " + e, e); + } + if (typeHandlerRegistry.hasTypeHandler(rm.getType())) { + resultObject = value; + } else if (value != null) { + metaResultObject.setValue(resultMapping.getProperty(), value); + } + return resultObject; + } + + private Object processResult(ResultSet rs, ResultMap rm, ResultMapping resultMapping, ResultLoaderRegistry lazyLoader, Object resultObject) throws SQLException { + if (resultMapping.getNestedQueryId() != null) { + Configuration configuration = mappedStatement.getConfiguration(); + MappedStatement nestedQuery = configuration.getMappedStatement(resultMapping.getNestedQueryId()); + Class parameterType = nestedQuery.getParameterMap().getType(); + Object parameterObject = prepareNestedParameterObject(rs, resultMapping, parameterType); + resultObject = processNestedSelectResult(nestedQuery, rm, resultMapping, lazyLoader, parameterObject, resultObject); + } else if (resultMapping.getNestedResultMapId() == null) { + resultObject = processSimpleResult(rs, rm, resultMapping, resultObject); + } + return resultObject; + } + + private Object processSimpleResult(ResultSet rs, ResultMap rm, ResultMapping resultMapping, Object resultObject) throws SQLException { + MetaObject metaResultObject = MetaObject.forObject(resultObject); + Object value = getPrimitiveResultMappingValue(rs, resultMapping); + if (typeHandlerRegistry.hasTypeHandler(rm.getType())) { + resultObject = value; + } else if (value != null) { + metaResultObject.setValue(resultMapping.getProperty(), value); + } + foundValues.set(value != null || foundValues.get()); + return resultObject; + } + + private Object processNestedJoinResults(ResultSet rs, List<ResultMapping> resultMappings, Object resultObject) { + CacheKey previousKey = currentNestedKey; + try { + currentNestedKey = createUniqueResultKey(resultMappings, resultObject, currentNestedKey); + if (nestedResultObjects.containsKey(currentNestedKey)) { + // Unique key is already known, so get the existing result object and process additional results. + resultObject = NO_VALUE; + } else if (currentNestedKey != null) { + // Unique key is NOT known, so create a new result object and then process additional results. + nestedResultObjects.put(currentNestedKey, resultObject); + } + Object knownResultObject = nestedResultObjects.get(currentNestedKey); + if (knownResultObject != null && knownResultObject != NO_VALUE) { + for (ResultMapping resultMapping : resultMappings) { + Configuration configuration = mappedStatement.getConfiguration(); + ResultMap nestedResultMap = configuration.getResultMap(resultMapping.getNestedResultMapId()); + if (nestedResultMap != null) { + try { + + // get the discriminated submap if it exists + nestedResultMap = resolveSubMap(rs, nestedResultMap); + + Class type = resultMapping.getJavaType(); + String propertyName = resultMapping.getProperty(); + + MetaObject metaObject = MetaObject.forObject(knownResultObject); + Object obj = metaObject.getValue(propertyName); + if (obj == null) { + if (type == null) { + type = metaObject.getSetterType(propertyName); + } + + try { + // create the object if is it a Collection. If not a Collection + // then we will just set the property to the object created + // in processing the nested result map + if (Collection.class.isAssignableFrom(type)) { + obj = objectFactory.create(type); + metaObject.setValue(propertyName, obj); + } + } catch (Exception e) { + throw new RuntimeException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + e, e); + } + } + + Object o = loadResultObject(rs, nestedResultMap); + if (o != null && o != NO_VALUE) { + if (obj != null && obj instanceof Collection) { + if (foundValues.get()) { + ((Collection) obj).add(o); + } + } else { + metaObject.setValue(propertyName, o); + } + } + } catch (SQLException e) { + throw new RuntimeException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e); + } + } + } + } + } finally { + currentNestedKey = previousKey; + } + return resultObject; + } + + private Object prepareNestedParameterObject(ResultSet rs, ResultMapping resultMapping, Class parameterType) throws SQLException { + Object parameterObject; + if (typeHandlerRegistry.hasTypeHandler(parameterType)) { + TypeHandler th = typeHandlerRegistry.getTypeHandler(parameterType); + parameterObject = th.getResult(rs, resultMapping.getColumn()); + } else { + if (parameterType == null) { + parameterObject = new HashMap(); + } else { + parameterObject = objectFactory.create(parameterType); + } + if (resultMapping.isCompositeResult()) { + MetaObject metaObject = MetaObject.forObject(parameterObject); + for (ResultMapping innerResultMapping : resultMapping.getComposites()) { + Class propType = metaObject.getSetterType(innerResultMapping.getProperty()); + TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propType); + Object propValue = typeHandler.getResult(rs, innerResultMapping.getColumn()); + metaObject.setValue(innerResultMapping.getProperty(), propValue); + } + } else { + String columnName = resultMapping.getColumn(); + TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(parameterType); + if (typeHandler == null) { + typeHandler = typeHandlerRegistry.getUnkownTypeHandler(); + } + parameterObject = typeHandler.getResult(rs, columnName); + } + } + return parameterObject; + } + + ////////////////////////////////////////// + // UTILITY METHODS + ////////////////////////////////////////// + + private ResultMap resolveSubMap(ResultSet rs, ResultMap rm) throws SQLException { + ResultMap subMap = rm; + Discriminator discriminator = rm.getDiscriminator(); + if (discriminator != null) { + ResultMapping resultMapping = discriminator.getResultMapping(); + Object value = getPrimitiveResultMappingValue(rs, resultMapping); + String subMapId = discriminator.getMapIdFor(String.valueOf(value)); + subMap = mappedStatement.getConfiguration().getResultMap(subMapId); + + if (subMap == null) { + subMap = rm; + } else if (subMap != rm) { + subMap = resolveSubMap(rs, subMap); + } + } + return subMap; + } + + private Object getPrimitiveResultMappingValue(ResultSet rs, ResultMapping resultMapping) throws SQLException { + Object value; + TypeHandler typeHandler = resultMapping.getTypeHandler(); + if (typeHandler != null) { + value = typeHandler.getResult(rs, resultMapping.getColumn()); + } else { + throw new RuntimeException("No type handler could be found to map the property '" + resultMapping.getProperty() + "' to the column '" + resultMapping.getColumn() + "'. One or both of the types, or the combination of types is not supported."); + } + return value; + } + + private CacheKey createUniqueResultKey(List<ResultMapping> resultMappings, Object resultObject, CacheKey parentCacheKey) { + if (resultObject == null) { + return null; + } else { + MetaObject metaResultObject = MetaObject.forObject(resultObject); + CacheKey cacheKey = new CacheKey(); + cacheKey.update(parentCacheKey); + boolean updated = false; + if (typeHandlerRegistry.hasTypeHandler(resultObject.getClass())) { + cacheKey.update(resultObject); + } else { + for (ResultMapping resultMapping : resultMappings) { + if (resultMapping.getNestedQueryId() == null) { + String propName = resultMapping.getProperty(); + if (propName != null) { + cacheKey.update(metaResultObject.getValue(propName)); + updated = true; + } + } + } + } + return updated ? cacheKey : null; + } + } + + private ResultSet getFirstResultSet(Statement statement) throws SQLException { + ResultSet rs = null; + boolean hasMoreResults = true; + while (hasMoreResults) { + rs = statement.getResultSet(); + if (rs != null) { + break; + } + // This is the messed up JDBC approach for determining if there are more results + hasMoreResults = !((!moveToNextResultsSafely(statement)) && (statement.getUpdateCount() == -1)); + } + return rs; + } + + private boolean moveToNextResultsSafely(Statement statement) throws SQLException { + if (mappedStatement.getConfiguration().isMultipleResultSetsEnabled()) { + return statement.getMoreResults(); + } + return false; + } + + private void skipResults(ResultSet rs, int skipResults) throws SQLException { + if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) { + if (skipResults > 0) { + rs.absolute(skipResults); + } + } else { + for (int i = 0; i < skipResults; i++) { + if (!rs.next()) break; + } + } + } + + private void closeResultSet(ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + // ignore + } + } + } + + private static class Reference<T> { + private T value; + private Reference(T value) { + this.value = value; + } + public T get() { + return value; + } + public void set(T value) { + this.value = value; + } + } +}
Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/PlatformTypeHolder.java URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/PlatformTypeHolder.java?rev=683745&view=auto ============================================================================== --- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/PlatformTypeHolder.java (added) +++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/PlatformTypeHolder.java Thu Aug 7 16:21:46 2008 @@ -0,0 +1,128 @@ +package org.apache.ibatis.executor.resultset; + +import java.math.BigDecimal; +import java.util.*; + +public class PlatformTypeHolder implements Map { + + private static final Set<Class> platformTypes = new HashSet<Class>() { + { + add(byte[].class); + add(byte.class); + add(short.class); + add(int.class); + add(long.class); + add(float.class); + add(double.class); + add(boolean.class); + add(Byte.class); + add(Short.class); + add(Integer.class); + add(Long.class); + add(Float.class); + add(Double.class); + add(Boolean.class); + + add(String.class); + add(BigDecimal.class); + add(Date.class); + add(Object.class); + + add(java.sql.Date.class); + add(java.sql.Time.class); + add(java.sql.Timestamp.class); + } + }; + + private Object key; + private Object value; + + public static boolean isPlatformType(Class type) { + return platformTypes.contains(type); + } + + public int size() { + return 1; + } + + public boolean isEmpty() { + return key == null && value == null; + } + + public boolean containsKey(Object other) { + return key == null ? other == null : key.equals(other); + } + + public boolean containsValue(Object other) { + return value == null ? other == null : value.equals(other); + } + + public Object get(Object key) { + return value; + } + + public Object put(Object key, Object value) { + Object old = this.value; + this.key = key; + this.value = value; + return old; + } + + public Object remove(Object key) { + Object old = this.value; + this.key = null; + this.value = null; + return old; + } + + public void putAll(Map t) { + for (Map.Entry e : (Set<Map.Entry>) t.entrySet()) { + this.key = e.getKey(); + this.value = e.getValue(); + } + } + + public void clear() { + this.key = null; + this.value = null; + } + + public Set keySet() { + return new HashSet() { + { + add(key); + } + }; + } + + public Collection values() { + return new ArrayList() { + { + add(value); + } + }; + } + + public Set entrySet() { + final Map.Entry entry = new Map.Entry() { + public Object getKey() { + return key; + } + + public Object getValue() { + return value; + } + + public Object setValue(Object v) { + Object old = value; + value = v; + return old; + } + }; + return new HashSet() { + { + add(entry); + } + }; + } +} Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/ResultSetHandler.java URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/ResultSetHandler.java?rev=683745&view=auto ============================================================================== --- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/ResultSetHandler.java (added) +++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/resultset/ResultSetHandler.java Thu Aug 7 16:21:46 2008 @@ -0,0 +1,12 @@ +package org.apache.ibatis.executor.resultset; + +import java.sql.*; +import java.util.List; + +public interface ResultSetHandler { + + List handleResultSets(Statement stmt) throws SQLException; + + void handleOutputParameters(CallableStatement cs) throws SQLException; + +} Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/BaseStatementHandler.java URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/BaseStatementHandler.java?rev=683745&view=auto ============================================================================== --- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/BaseStatementHandler.java (added) +++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/BaseStatementHandler.java Thu Aug 7 16:21:46 2008 @@ -0,0 +1,126 @@ +package org.apache.ibatis.executor.statement; + +import org.apache.ibatis.mapping.Configuration; +import org.apache.ibatis.executor.*; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.executor.result.ResultHandler; +import org.apache.ibatis.executor.resultset.ResultSetHandler; +import org.apache.ibatis.mapping.*; +import org.apache.ibatis.reflection.ObjectFactory; +import org.apache.ibatis.type.TypeHandlerRegistry; + +import java.sql.*; + +public abstract class BaseStatementHandler implements StatementHandler { + + protected final ErrorContext errorContext; + protected final ObjectFactory objectFactory; + protected final TypeHandlerRegistry typeHandlerRegistry; + protected final ResultSetHandler resultSetHandler; + protected final ParameterHandler parameterHandler; + + protected final Executor executor; + protected final MappedStatement mappedStatement; + protected final Object parameterObject; + protected final int rowOffset; + protected final int rowLimit; + protected final String sql; + + protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, int rowOffset, int rowLimit, ResultHandler resultHandler) { + this.executor = executor; + this.mappedStatement = mappedStatement; + this.parameterObject = parameterObject; + this.rowOffset = rowOffset; + this.rowLimit = rowLimit; + + Configuration configuration = mappedStatement.getConfiguration(); + this.errorContext = new ErrorContext(); + this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); + this.objectFactory = configuration.getObjectFactory(); + this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject); + this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowOffset, rowLimit, parameterHandler, resultHandler); + + this.sql = mappedStatement.getSql(parameterObject); + } + + public String getSql() { + return sql; + } + + public ParameterHandler getParameterHandler() { + return parameterHandler; + } + + public Statement prepare(Connection connection) + throws SQLException { + Statement statement = null; + try { + statement = instantiateStatement(connection); + setStatementTimeout(statement); + setFetchSize(statement); + return statement; + } catch (SQLException e) { + closeStatement(statement); + throw e; + } catch (Exception e) { + closeStatement(statement); + throw new RuntimeException("Error preparing statement. Cause: " + e, e); + } + } + + protected abstract Statement instantiateStatement(Connection connection) + throws SQLException; + + + protected Integer processGeneratedKeys(MappedStatement ms, Statement stmt, Object parameter) throws SQLException { + if (ms.getConfiguration().isGeneratedKeysEnabled()) { + ResultSet rs = stmt.getGeneratedKeys(); + try { + while (rs.next()) { + Object object = rs.getObject(1); + if (object != null) { + return Integer.parseInt(object.toString()); + } + } + } finally { + try { + if (rs != null) rs.close(); + } catch (Exception e) { + //ignore + } + } + } + return null; + } + + protected void setStatementTimeout(Statement stmt) + throws SQLException { + Integer timeout = mappedStatement.getTimeout(); + Integer defaultTimeout = mappedStatement.getConfiguration().getDefaultStatementTimeout(); + if (timeout != null) { + stmt.setQueryTimeout(timeout); + } else if (defaultTimeout != null) { + stmt.setQueryTimeout(defaultTimeout); + } + } + + protected void setFetchSize(Statement stmt) + throws SQLException { + Integer fetchSize = mappedStatement.getFetchSize(); + if (fetchSize != null) { + stmt.setFetchSize(fetchSize); + } + } + + protected void closeStatement(Statement statement) { + try { + if (statement != null) { + statement.close(); + } + } catch (SQLException e) { + //ignore + } + + } + +} Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/CallableStatementHandler.java URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/CallableStatementHandler.java?rev=683745&view=auto ============================================================================== --- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/CallableStatementHandler.java (added) +++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/CallableStatementHandler.java Thu Aug 7 16:21:46 2008 @@ -0,0 +1,72 @@ +package org.apache.ibatis.executor.statement; + +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.result.ResultHandler; +import org.apache.ibatis.mapping.*; +import org.apache.ibatis.type.JdbcType; + +import java.sql.*; +import java.util.List; + +public class CallableStatementHandler extends BaseStatementHandler { + + public CallableStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, int rowOffset, int rowLimit, ResultHandler resultHandler) { + super(executor, mappedStatement, parameter, rowOffset, rowLimit, resultHandler); + } + + public int update(Statement statement) + throws SQLException { + CallableStatement cs = (CallableStatement) statement; + cs.execute(); + int rows = cs.getUpdateCount(); + resultSetHandler.handleOutputParameters(cs); + return rows; + } + + public void batch(Statement statement) + throws SQLException { + CallableStatement cs = (CallableStatement) statement; + cs.addBatch(); + } + + public List query(Statement statement, ResultHandler resultHandler) + throws SQLException { + CallableStatement cs = (CallableStatement) statement; + cs.execute(); + resultSetHandler.handleOutputParameters(cs); + return resultSetHandler.handleResultSets(cs); + } + + protected Statement instantiateStatement(Connection connection) throws SQLException { + if (mappedStatement.getResultSetType() != null) { + return connection.prepareCall(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); + } else { + return connection.prepareCall(sql); + } + } + + public void parameterize(Statement statement) throws SQLException { + registerOutputParameters((CallableStatement) statement, mappedStatement); + parameterHandler.setParameters((CallableStatement) statement); + } + + private void registerOutputParameters(CallableStatement cs, MappedStatement ms) throws SQLException { + List<ParameterMapping> parameterMappings = ms.getDynamicParameterMappings(parameterObject); + for (int i = 0, n = parameterMappings.size(); i < n; i++) { + ParameterMapping parameterMapping = parameterMappings.get(i); + if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) { + if (null == parameterMapping.getJdbcType()) { + throw new SQLException("The JDBC Type must be specified for output parameterArray. Paramter: " + parameterMapping.getProperty()); + } else { + if (parameterMapping.getNumericScale() != null && (parameterMapping.getJdbcType() == JdbcType.NUMERIC || parameterMapping.getJdbcType() == JdbcType.DECIMAL)) { + cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getNumericScale()); + } else { + //cs.registerOutParameter(i + 1, parameter.getJdbcType().TYPE_CODE, parameter.getJdbcType().toString()); + cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE); + } + } + } + } + } + +} Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/PreparedStatementHandler.java URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/PreparedStatementHandler.java?rev=683745&view=auto ============================================================================== --- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/PreparedStatementHandler.java (added) +++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/PreparedStatementHandler.java Thu Aug 7 16:21:46 2008 @@ -0,0 +1,55 @@ +package org.apache.ibatis.executor.statement; + +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.result.ResultHandler; +import org.apache.ibatis.mapping.MappedStatement; + +import java.sql.*; +import java.util.List; + +public class PreparedStatementHandler extends BaseStatementHandler { + + public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, int rowOffset, int rowLimit, ResultHandler resultHandler) { + super(executor, mappedStatement, parameter, rowOffset, rowLimit, resultHandler); + } + + public int update(Statement statement) + throws SQLException { + PreparedStatement ps = (PreparedStatement) statement; + ps.execute(); + int result = ps.getUpdateCount(); + if (mappedStatement.getConfiguration().isGeneratedKeysEnabled()) { + result = processGeneratedKeys(mappedStatement, ps, parameterObject); + } + return result; + } + + public void batch(Statement statement) + throws SQLException { + PreparedStatement ps = (PreparedStatement) statement; + ps.addBatch(); + } + + public List query(Statement statement, ResultHandler resultHandler) + throws SQLException { + PreparedStatement ps = (PreparedStatement) statement; + ps.execute(); + return resultSetHandler.handleResultSets(ps); + } + + protected Statement instantiateStatement(Connection connection) throws SQLException { + if (mappedStatement.getConfiguration().isGeneratedKeysEnabled()) { + return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); + } else if (mappedStatement.getResultSetType() != null) { + return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); + } else { + return connection.prepareStatement(sql); + } + } + + public void parameterize(Statement statement) + throws SQLException { + parameterHandler.setParameters((PreparedStatement) statement); + } + +} Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/RoutingStatementHandler.java URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/RoutingStatementHandler.java?rev=683745&view=auto ============================================================================== --- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/RoutingStatementHandler.java (added) +++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/RoutingStatementHandler.java Thu Aug 7 16:21:46 2008 @@ -0,0 +1,60 @@ +package org.apache.ibatis.executor.statement; + +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.executor.result.ResultHandler; +import org.apache.ibatis.mapping.MappedStatement; + +import java.sql.*; +import java.util.List; + +public class RoutingStatementHandler implements StatementHandler { + + private final StatementHandler delegate; + + public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, int rowOffset, int rowLimit, ResultHandler resultHandler) { + + switch (ms.getStatementType()) { + case STATEMENT: + delegate = new SimpleStatementHandler(executor, ms, parameter, rowOffset, rowLimit, resultHandler); + break; + case PREPARED: + delegate = new PreparedStatementHandler(executor, ms, parameter, rowOffset, rowLimit, resultHandler); + break; + case CALLABLE: + delegate = new CallableStatementHandler(executor, ms, parameter, rowOffset, rowLimit, resultHandler); + break; + default: + throw new RuntimeException("Unknown statement type: " + ms.getStatementType()); + } + + } + + public Statement prepare(Connection connection) throws SQLException { + return delegate.prepare(connection); + } + + public void parameterize(Statement statement) throws SQLException { + delegate.parameterize(statement); + } + + public void batch(Statement statement) throws SQLException { + delegate.batch(statement); + } + + public int update(Statement statement) throws SQLException { + return delegate.update(statement); + } + + public List query(Statement statement, ResultHandler resultHandler) throws SQLException { + return delegate.query(statement, resultHandler); + } + + public String getSql() { + return delegate.getSql(); + } + + public ParameterHandler getParameterHandler() { + return delegate.getParameterHandler(); + } +} Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/SimpleStatementHandler.java URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/SimpleStatementHandler.java?rev=683745&view=auto ============================================================================== --- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/SimpleStatementHandler.java (added) +++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/SimpleStatementHandler.java Thu Aug 7 16:21:46 2008 @@ -0,0 +1,53 @@ +package org.apache.ibatis.executor.statement; + +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.result.ResultHandler; +import org.apache.ibatis.mapping.MappedStatement; + +import java.sql.*; +import java.util.List; + +public class SimpleStatementHandler extends BaseStatementHandler { + + public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, int rowOffset, int rowLimit, ResultHandler resultHandler) { + super(executor, mappedStatement, parameter, rowOffset, rowLimit, resultHandler); + } + + public int update(Statement statement) + throws SQLException { + if (mappedStatement.getConfiguration().isGeneratedKeysEnabled()) { + statement.execute(mappedStatement.getSql(parameterObject), Statement.RETURN_GENERATED_KEYS); + } else { + statement.execute(mappedStatement.getSql(parameterObject)); + } + int result = statement.getUpdateCount(); + if (mappedStatement.getConfiguration().isGeneratedKeysEnabled()) { + result = processGeneratedKeys(mappedStatement, statement, parameterObject); + } + return result; + } + + public void batch(Statement statement) + throws SQLException { + statement.addBatch(mappedStatement.getSql(parameterObject)); + } + + public List query(Statement statement, ResultHandler resultHandler) + throws SQLException { + statement.execute(mappedStatement.getSql(parameterObject)); + return resultSetHandler.handleResultSets(statement); + } + + protected Statement instantiateStatement(Connection connection) throws SQLException { + if (mappedStatement.getResultSetType() != null) { + return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); + } else { + return connection.createStatement(); + } + } + + public void parameterize(Statement statement) throws SQLException { + // N/A + } + +} Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/StatementHandler.java URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/StatementHandler.java?rev=683745&view=auto ============================================================================== --- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/StatementHandler.java (added) +++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/executor/statement/StatementHandler.java Thu Aug 7 16:21:46 2008 @@ -0,0 +1,30 @@ +package org.apache.ibatis.executor.statement; + +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.executor.result.ResultHandler; + +import java.sql.*; +import java.util.List; + +public interface StatementHandler { + + Statement prepare(Connection connection) + throws SQLException; + + void parameterize(Statement statement) + throws SQLException; + + void batch(Statement statement) + throws SQLException; + + int update(Statement statement) + throws SQLException; + + List query(Statement statement, ResultHandler resultHandler) + throws SQLException; + + String getSql(); + + ParameterHandler getParameterHandler(); + +} Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/io/Resources.java URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/io/Resources.java?rev=683745&view=auto ============================================================================== --- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/io/Resources.java (added) +++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/io/Resources.java Thu Aug 7 16:21:46 2008 @@ -0,0 +1,264 @@ +package org.apache.ibatis.io; + +import java.io.*; +import java.net.*; +import java.nio.charset.Charset; +import java.util.Properties; + +/** + * A class to simplify access to resources through the classloader. + */ +public class Resources { + + private static ClassLoader defaultClassLoader; + + /** + * Charset to use when calling getResourceAsReader. + * null means use the system default. + */ + private static Charset charset; + + private Resources() { + } + + /** + * Returns the default classloader (may be null). + * + * @return The default classloader + */ + public static ClassLoader getDefaultClassLoader() { + return defaultClassLoader; + } + + /** + * Sets the default classloader + * + * @param defaultClassLoader - the new default ClassLoader + */ + public static void setDefaultClassLoader(ClassLoader defaultClassLoader) { + Resources.defaultClassLoader = defaultClassLoader; + } + + /** + * Returns the URL of the resource on the classpath + * + * @param resource The resource to find + * @return The resource + * @throws java.io.IOException If the resource cannot be found or read + */ + public static URL getResourceURL(String resource) throws IOException { + return getResourceURL(getClassLoader(), resource); + } + + /** + * Returns the URL of the resource on the classpath + * + * @param loader The classloader used to fetch the resource + * @param resource The resource to find + * @return The resource + * @throws java.io.IOException If the resource cannot be found or read + */ + public static URL getResourceURL(ClassLoader loader, String resource) throws IOException { + URL url = null; + if (loader != null) url = loader.getResource(resource); + if (url == null) url = ClassLoader.getSystemResource(resource); + if (url == null) throw new IOException("Could not find resource " + resource); + return url; + } + + /** + * Returns a resource on the classpath as a Stream object + * + * @param resource The resource to find + * @return The resource + * @throws java.io.IOException If the resource cannot be found or read + */ + public static InputStream getResourceAsStream(String resource) throws IOException { + return getResourceAsStream(getClassLoader(), resource); + } + + /** + * Returns a resource on the classpath as a Stream object + * + * @param loader The classloader used to fetch the resource + * @param resource The resource to find + * @return The resource + * @throws java.io.IOException If the resource cannot be found or read + */ + public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException { + InputStream in = null; + if (loader != null) in = loader.getResourceAsStream(resource); + if (in == null) in = ClassLoader.getSystemResourceAsStream(resource); + if (in == null) throw new IOException("Could not find resource " + resource); + return in; + } + + /** + * Returns a resource on the classpath as a Properties object + * + * @param resource The resource to find + * @return The resource + * @throws java.io.IOException If the resource cannot be found or read + */ + public static Properties getResourceAsProperties(String resource) + throws IOException { + Properties props = new Properties(); + InputStream in = getResourceAsStream(resource); + props.load(in); + in.close(); + return props; + } + + /** + * Returns a resource on the classpath as a Properties object + * + * @param loader The classloader used to fetch the resource + * @param resource The resource to find + * @return The resource + * @throws java.io.IOException If the resource cannot be found or read + */ + public static Properties getResourceAsProperties(ClassLoader loader, String resource) + throws IOException { + Properties props = new Properties(); + InputStream in = getResourceAsStream(loader, resource); + props.load(in); + in.close(); + return props; + } + + /** + * Returns a resource on the classpath as a Reader object + * + * @param resource The resource to find + * @return The resource + * @throws java.io.IOException If the resource cannot be found or read + */ + public static Reader getResourceAsReader(String resource) throws IOException { + Reader reader; + if (charset == null) { + reader = new InputStreamReader(getResourceAsStream(resource)); + } else { + reader = new InputStreamReader(getResourceAsStream(resource), charset); + } + + return reader; + } + + /** + * Returns a resource on the classpath as a Reader object + * + * @param loader The classloader used to fetch the resource + * @param resource The resource to find + * @return The resource + * @throws java.io.IOException If the resource cannot be found or read + */ + public static Reader getResourceAsReader(ClassLoader loader, String resource) throws IOException { + Reader reader; + if (charset == null) { + reader = new InputStreamReader(getResourceAsStream(loader, resource)); + } else { + reader = new InputStreamReader(getResourceAsStream(loader, resource), charset); + } + + return reader; + } + + /** + * Returns a resource on the classpath as a File object + * + * @param resource The resource to find + * @return The resource + * @throws java.io.IOException If the resource cannot be found or read + */ + public static File getResourceAsFile(String resource) throws IOException { + return new File(getResourceURL(resource).getFile()); + } + + /** + * Returns a resource on the classpath as a File object + * + * @param loader - the classloader used to fetch the resource + * @param resource - the resource to find + * @return The resource + * @throws java.io.IOException If the resource cannot be found or read + */ + public static File getResourceAsFile(ClassLoader loader, String resource) throws IOException { + return new File(getResourceURL(loader, resource).getFile()); + } + + /** + * Gets a URL as an input stream + * + * @param urlString - the URL to get + * @return An input stream with the data from the URL + * @throws java.io.IOException If the resource cannot be found or read + */ + public static InputStream getUrlAsStream(String urlString) throws IOException { + URL url = new URL(urlString); + URLConnection conn = url.openConnection(); + return conn.getInputStream(); + } + + /** + * Gets a URL as a Reader + * + * @param urlString - the URL to get + * @return A Reader with the data from the URL + * @throws java.io.IOException If the resource cannot be found or read + */ + public static Reader getUrlAsReader(String urlString) throws IOException { + return new InputStreamReader(getUrlAsStream(urlString)); + } + + /** + * Gets a URL as a Properties object + * + * @param urlString - the URL to get + * @return A Properties object with the data from the URL + * @throws java.io.IOException If the resource cannot be found or read + */ + public static Properties getUrlAsProperties(String urlString) throws IOException { + Properties props = new Properties(); + InputStream in = getUrlAsStream(urlString); + props.load(in); + in.close(); + return props; + } + + /** + * Loads a class + * + * @param className - the class to fetch + * @return The loaded class + * @throws ClassNotFoundException If the class cannot be found (duh!) + */ + public static Class classForName(String className) throws ClassNotFoundException { + Class clazz = null; + try { + clazz = getClassLoader().loadClass(className); + } catch (Exception e) { + // Ignore. Failsafe below. + } + if (clazz == null) { + clazz = Class.forName(className); + } + return clazz; + } + + private static ClassLoader getClassLoader() { + if (defaultClassLoader != null) { + return defaultClassLoader; + } else { + return Thread.currentThread().getContextClassLoader(); + } + } + + public static Charset getCharset() { + return charset; + } + + public static void setCharset(Charset charset) { + Resources.charset = charset; + } + +}