Index: src/net/lemnik/eodsql/QuickQueryUtil.java =================================================================== --- src/net/lemnik/eodsql/QuickQueryUtil.java (revision 217) +++ src/net/lemnik/eodsql/QuickQueryUtil.java (working copy) @@ -22,6 +22,9 @@ import net.lemnik.eodsql.spi.ResultSetResource; import net.lemnik.eodsql.spi.StatementResource; +import net.lemnik.eodsql.spi.util.MapDataObjectBinding; +import net.lemnik.eodsql.spi.util.NoDataObjectBinding; +import net.lemnik.eodsql.spi.util.DataObjectBinding; import net.lemnik.eodsql.spi.util.Query; import net.lemnik.eodsql.spi.util.DataSetWrapper; import net.lemnik.eodsql.spi.util.ResultSetWrapper; @@ -70,6 +73,16 @@ return 0; } + public Class resultSetBinding() + { + return NoDataObjectBinding.class; + } + + public Class[] parameterBindings() + { + return new Class[0]; + } + }; private QuickQueryUtil() { Index: src/net/lemnik/eodsql/spi/util/ArrayWrapper.java =================================================================== --- src/net/lemnik/eodsql/spi/util/ArrayWrapper.java (revision 217) +++ src/net/lemnik/eodsql/spi/util/ArrayWrapper.java (working copy) @@ -226,8 +226,15 @@ final Class arrayType = (Class)genericType; - return new ArrayWrapper(DataObjectBinding.getDataObjectBinding( - arrayType.getComponentType(), bindingType)); + if (parameters.containsKey(PARAMETER_CUSTOM_DATA_OBJECT_BINDING)) { + final DataObjectBinding + binding = (DataObjectBinding) parameters.get(PARAMETER_CUSTOM_DATA_OBJECT_BINDING); + binding.setObjectType(arrayType.getComponentType()); + return new ArrayWrapper(binding); + } else { + return new ArrayWrapper(DataObjectBinding.getDataObjectBinding( + arrayType.getComponentType(), bindingType)); + } } } Index: src/net/lemnik/eodsql/spi/util/DataIteratorWrapper.java =================================================================== --- src/net/lemnik/eodsql/spi/util/DataIteratorWrapper.java (revision 217) +++ src/net/lemnik/eodsql/spi/util/DataIteratorWrapper.java (working copy) @@ -115,6 +115,7 @@ return true; } + @SuppressWarnings("unchecked") public ResultSetWrapper create( final Type genericType, final Map parameters) { @@ -122,10 +123,17 @@ final ParameterizedType parameterType = (ParameterizedType)genericType; final Class clazz = (Class)parameterType.getActualTypeArguments()[0]; - final DataObjectBinding binding = DataObjectBinding. - getDataObjectBinding( - clazz, - AbstractResultSetWrapper.getBindingType(parameters)); + final DataObjectBinding binding; + if (parameters.containsKey(PARAMETER_CUSTOM_DATA_OBJECT_BINDING)) { + final DataObjectBinding + customBinding = (DataObjectBinding) parameters.get(PARAMETER_CUSTOM_DATA_OBJECT_BINDING); + customBinding.setObjectType((Class) clazz); + binding = customBinding; + } else { + binding = DataObjectBinding.getDataObjectBinding( + clazz, + AbstractResultSetWrapper.getBindingType(parameters)); + } return new DataIteratorWrapper(binding, parameters); } Index: src/net/lemnik/eodsql/spi/util/DataSetWrapper.java =================================================================== --- src/net/lemnik/eodsql/spi/util/DataSetWrapper.java (revision 217) +++ src/net/lemnik/eodsql/spi/util/DataSetWrapper.java (working copy) @@ -85,8 +85,15 @@ final Class dataObjectType, final Map parameters) { - binding = DataObjectBinding.getDataObjectBinding(dataObjectType, - AbstractResultSetWrapper.getBindingType(parameters)); + if (parameters.containsKey(PARAMETER_CUSTOM_DATA_OBJECT_BINDING)) { + final DataObjectBinding + customBinding = (DataObjectBinding) parameters.get(PARAMETER_CUSTOM_DATA_OBJECT_BINDING); + customBinding.setObjectType(dataObjectType); + binding = customBinding; + } else { + binding = DataObjectBinding.getDataObjectBinding(dataObjectType, + AbstractResultSetWrapper.getBindingType(parameters)); + } disconnected = isDisconnected(parameters); updatable = isUpdatable(parameters); Index: src/net/lemnik/eodsql/spi/util/NoDataObjectBinding.java =================================================================== --- src/net/lemnik/eodsql/spi/util/NoDataObjectBinding.java (revision 0) +++ src/net/lemnik/eodsql/spi/util/NoDataObjectBinding.java (revision 0) @@ -0,0 +1,23 @@ +package net.lemnik.eodsql.spi.util; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import net.lemnik.eodsql.EoDException; + +/** + * A tag class indicating that no custom data object binding is to be used. + * + * @author Bernd Rinn + */ +public final class NoDataObjectBinding extends DataObjectBinding { + @Override + public void marshall(T from, ResultSet results) throws SQLException, EoDException { + throw new UnsupportedOperationException(); + } + + @Override + public void unmarshall(ResultSet row, T into) throws SQLException, EoDException { + throw new UnsupportedOperationException(); + } +} Property changes on: src/net/lemnik/eodsql/spi/util/NoDataObjectBinding.java ___________________________________________________________________ Added: svn:eol-style + native Index: src/net/lemnik/eodsql/spi/util/ResultSetWrapper.java =================================================================== --- src/net/lemnik/eodsql/spi/util/ResultSetWrapper.java (revision 217) +++ src/net/lemnik/eodsql/spi/util/ResultSetWrapper.java (working copy) @@ -63,6 +63,14 @@ public static final String PARAMETER_BINDING_TYPE = "net.lemnik.eodsql.spi.util.ResultSetWrapper#bindingType"; + /** + *

+ * This parameter allows to set a custom class for data object binding for this query. The + * default value {@link NoDataObjectBinding} means: no custom binding is requested. + *

+ */ + public static final String PARAMETER_CUSTOM_DATA_OBJECT_BINDING = "net.lemnik.eodsql.spi.util.ResultSetWrapper#binding"; + private static final Set FACTORIES = new LinkedHashSet(4); private static final Map NO_PARAMETERS = @@ -112,16 +120,20 @@ @SuppressWarnings("unchecked") private static ResultSetWrapper getResultSetWrapperForSimpleClass( final Class type, - final Type genericReturnType, - final BindingType bindingType) { + final BindingType bindingType, + final DataObjectBinding customBinding) { + if (customBinding != null) { + customBinding.setObjectType(type); + return new SingleRowResultSetWrapper(customBinding); + } + final TypeMapper mapper = QueryTool.getTypeMap().get(type); if(mapper != null) { return getTypeMapperResultSetWrapper(mapper, (Class)type); } else { - return getDataObjectResultSetWrapper((Class)genericReturnType, - bindingType); + return getDataObjectResultSetWrapper(type, bindingType); } } @@ -185,6 +197,23 @@ } /** + * Returns the custom binding, or null, if no custom binding is specified in the + * parameter {@code Map}. + * + * @param parameters the parameters as specified in {@link #get(java.lang.reflect.Type, java.util.Map)} + * @return the {@code DataObjectBinding} that should be used according to the parameter {@code Map} + */ + protected static DataObjectBinding getCustomBinding( + final Map parameters) { + + if(parameters.containsKey(DataSetWrapper.PARAMETER_CUSTOM_DATA_OBJECT_BINDING)) { + return (DataObjectBinding)parameters.get(DataSetWrapper.PARAMETER_CUSTOM_DATA_OBJECT_BINDING); + } + + return null; + } + + /** * Wrap the given {@code Context} in whatever type this {@code ResultSetWrapper} should * return. This method by default makes a call to {@link #wrap(java.sql.ResultSet)}, but can * be overriden if required (for example to switch of {@link Context#setAutoclose(boolean) @@ -353,8 +382,8 @@ return getResultSetWrapperForSimpleClass( type, - genericReturnType, - getBindingType(parameters)); + getBindingType(parameters), + getCustomBinding(parameters)); } else { for(final Factory f : FACTORIES) { if(f.isTypeConstructable(genericReturnType, parameters)) { Index: src/net/lemnik/eodsql/spi/util/DataObjectBinding.java =================================================================== --- src/net/lemnik/eodsql/spi/util/DataObjectBinding.java (revision 217) +++ src/net/lemnik/eodsql/spi/util/DataObjectBinding.java (working copy) @@ -339,6 +339,9 @@ try { @SuppressWarnings(value = "unchecked") DataObjectBinding binding = (DataObjectBinding)bindingClass.newInstance(); + if (binding.getObjectType() == null) { + binding.setObjectType(dataObject); + } return trySetBindingType(binding, bindingType); } catch(InstantiationException instantiationException) { throw new RuntimeException(instantiationException); Index: src/net/lemnik/eodsql/spi/util/CollectionWrapper.java =================================================================== --- src/net/lemnik/eodsql/spi/util/CollectionWrapper.java (revision 217) +++ src/net/lemnik/eodsql/spi/util/CollectionWrapper.java (working copy) @@ -140,8 +140,15 @@ final Class dataObject = (Class) paramType.getActualTypeArguments()[0]; - final DataObjectBinding binding = DataObjectBinding. - getDataObjectBinding(dataObject, bindingType); + final DataObjectBinding binding; + if (parameters.containsKey(PARAMETER_CUSTOM_DATA_OBJECT_BINDING)) { + final DataObjectBinding + customBinding = (DataObjectBinding) parameters.get(PARAMETER_CUSTOM_DATA_OBJECT_BINDING); + customBinding.setObjectType((Class) dataObject); + binding = customBinding; + } else { + binding = DataObjectBinding.getDataObjectBinding(dataObject, bindingType); + } return new CollectionWrapper( (Class)paramType.getRawType(), Index: src/net/lemnik/eodsql/Call.java =================================================================== --- src/net/lemnik/eodsql/Call.java (revision 217) +++ src/net/lemnik/eodsql/Call.java (working copy) @@ -5,6 +5,9 @@ import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy; +import net.lemnik.eodsql.spi.util.NoDataObjectBinding; +import net.lemnik.eodsql.spi.util.DataObjectBinding; + /** *

* The {@code @Call} annotation is used to mark methods within a @@ -112,4 +115,24 @@ */ Class cache() default ArrayDataSetCache.class; + /** + *

+ * A custom data object binding to be used on the result set of this query. The default value + * {@link NoDataObjectBinding} indicates that no custom binding is to be used. + *

+ * + * @since 2.1 + */ + Class resultSetBinding() default NoDataObjectBinding.class; + + /** + *

+ * The custom type bindings to be used on the parameters of this query. The binding is matched + * by position. The default type binding will be used for each parameter that has no + * parameterBindings entry or an entry that is of type {@link TypeMapper} itself. + *

+ * + * @since 2.1 + */ + Class[] parameterBindings() default {}; } Index: src/net/lemnik/eodsql/Select.java =================================================================== --- src/net/lemnik/eodsql/Select.java (revision 217) +++ src/net/lemnik/eodsql/Select.java (working copy) @@ -9,6 +9,8 @@ import java.sql.Statement; import net.lemnik.eodsql.spi.MethodImplementation; +import net.lemnik.eodsql.spi.util.NoDataObjectBinding; +import net.lemnik.eodsql.spi.util.DataObjectBinding; /** *

@@ -156,5 +158,25 @@ * @since 2.1 */ int fetchSize() default 0; + + /** + *

+ * The custom type bindings to be used on the parameters of this query. The binding is matched + * by position. The default type binding will be used for each parameter that has no + * parameterBindings entry or an entry that is of type {@link TypeMapper} itself. + *

+ * + * @since 2.1 + */ + Class[] parameterBindings() default {}; + /** + *

+ * A custom data object binding to be used on the result set of this query. The default value + * {@link NoDataObjectBinding} indicates that no custom binding is to be used. + *

+ * + * @since 2.1 + */ + Class resultSetBinding() default NoDataObjectBinding.class; } Index: src/net/lemnik/eodsql/impl/CallMethodImplementation.java =================================================================== --- src/net/lemnik/eodsql/impl/CallMethodImplementation.java (revision 217) +++ src/net/lemnik/eodsql/impl/CallMethodImplementation.java (working copy) @@ -22,6 +22,7 @@ import net.lemnik.eodsql.spi.MethodImplementation; import net.lemnik.eodsql.spi.MethodImplementationFactory; +import net.lemnik.eodsql.spi.util.NoDataObjectBinding; import net.lemnik.eodsql.spi.util.Query; import net.lemnik.eodsql.spi.util.DataSetWrapper; import net.lemnik.eodsql.spi.util.ResultSetWrapper; @@ -44,6 +45,9 @@ } query = Query.getQuery(queryString, method.getParameterTypes()); + + setParameterMappers(call.parameterBindings()); + returnVoid = method.getReturnType() == Void.TYPE; if(!returnVoid) { @@ -73,6 +77,17 @@ parameters.put(DataIteratorWrapper.PARAMETER_RUBBERSTAMP, Boolean.FALSE); } + if (call.resultSetBinding() != NoDataObjectBinding.class) { + try { + parameters.put(DataSetWrapper.PARAMETER_CUSTOM_DATA_OBJECT_BINDING, + call.resultSetBinding().newInstance()); + } catch (Exception ex) + { + throw new InvalidQueryException("DataObjectBinding classes must have a " + + "default (null) constructor: " + call.resultSetBinding().getCanonicalName(), ex); + } + } + parameters.put(DataSetWrapper.PARAMETER_CACHE_CLASS, call.cache()); return parameters; Index: src/net/lemnik/eodsql/impl/SelectMethodImplementation.java =================================================================== --- src/net/lemnik/eodsql/impl/SelectMethodImplementation.java (revision 217) +++ src/net/lemnik/eodsql/impl/SelectMethodImplementation.java (working copy) @@ -21,6 +21,7 @@ import net.lemnik.eodsql.spi.MethodImplementation; import net.lemnik.eodsql.spi.MethodImplementationFactory; +import net.lemnik.eodsql.spi.util.NoDataObjectBinding; import net.lemnik.eodsql.spi.util.Query; import net.lemnik.eodsql.spi.util.ResultSetWrapper; import net.lemnik.eodsql.spi.util.DataIteratorWrapper; @@ -45,6 +46,8 @@ query = Query.getQuery(queryString, method.getParameterTypes()); + setParameterMappers(select.parameterBindings()); + final Map parameters = extractReturnTypeMapperParameters(select); final Type returnType = method.getGenericReturnType(); @@ -79,6 +82,17 @@ } else { parameters.put(DataIteratorWrapper.PARAMETER_RUBBERSTAMP, Boolean.FALSE); } + + if (select.resultSetBinding() != NoDataObjectBinding.class) { + try { + parameters.put(DataSetWrapper.PARAMETER_CUSTOM_DATA_OBJECT_BINDING, + select.resultSetBinding().newInstance()); + } catch (Exception ex) + { + throw new InvalidQueryException("DataObjectBinding classes must have a " + + "default (null) constructor: " + select.resultSetBinding().getCanonicalName(), ex); + } + } parameters.put(DataSetWrapper.PARAMETER_CACHE_CLASS, select.cache()); Index: src/net/lemnik/eodsql/impl/UpdateMethodImplementation.java =================================================================== --- src/net/lemnik/eodsql/impl/UpdateMethodImplementation.java (revision 217) +++ src/net/lemnik/eodsql/impl/UpdateMethodImplementation.java (working copy) @@ -48,6 +48,8 @@ query = Query.getQuery(queryString, method.getParameterTypes()); + setParameterMappers(update.parameterBindings()); + final Map parameters = extractReturnTypeMapperParameters(update); if(keys != GeneratedKeys.NO_KEYS_RETURNED) { Index: src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java =================================================================== --- src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java (revision 217) +++ src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java (working copy) @@ -1,6 +1,8 @@ package net.lemnik.eodsql.impl; import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.sql.Connection; import java.sql.SQLException; @@ -61,30 +63,63 @@ protected TypeMapper[] getParameterMappers() { TypeMapper[] mappers = parameterMappers; if(mappers == null) { - mappers = getParameterTypeMappers(query); + mappers = getParameterTypeMappers(query, null); parameterMappers = mappers; } return mappers; } - protected static TypeMapper[] getParameterTypeMappers(final Query query) { + protected void setParameterMappers(final Class[] customTypeMapperClasses) { + parameterMappers = getParameterTypeMappers(query, customTypeMapperClasses); + } + + protected static TypeMapper[] getParameterTypeMappers(final Query query, + final Class[] customTypeMapperClasses) { final int parameters = query.getParameterCount(); final TypeMapper[] mappers = new TypeMapper[parameters]; final Map knownMappers = QueryTool.getTypeMap(); for(int i = 0; i < parameters; i++) { - final TypeMapper mapper = knownMappers.get(query.getParameterType(i)); - - if(mapper != null) { - mappers[i] = mapper; + final Constructor customMapperConstructor + = getConstructor(customTypeMapperClasses, i); + if (customMapperConstructor != null) { + try { + mappers[i] = customMapperConstructor.newInstance(); + } catch (Exception ex) { + throw new InvalidQueryException("Cannot construct custom type mapper " + + customMapperConstructor.getName(), ex); + } } else { - throw new InvalidQueryException("Unknown primitive type: " + - query.getParameterType(i).getName()); + final TypeMapper mapper = knownMappers.get(query.getParameterType(i)); + + if(mapper != null) { + mappers[i] = mapper; + } else { + throw new InvalidQueryException("Unknown primitive type: " + + query.getParameterType(i).getName()); + } } } return mappers; } + + private static Constructor getConstructor( + Class[] customTypeMapperClasses, int i) { + if (customTypeMapperClasses == null || i >= customTypeMapperClasses.length) { + return null; + } + final Class customTypeMapperClass = customTypeMapperClasses[i]; + if (customTypeMapperClass.isInterface()) { + return null; + } + try { + return customTypeMapperClass.getConstructor(); + } catch (Exception ex) { + throw new IllegalArgumentException("TypeMapper classes must have a default " + + "(null) constructor", ex); + } + } } Index: src/net/lemnik/eodsql/Update.java =================================================================== --- src/net/lemnik/eodsql/Update.java (revision 217) +++ src/net/lemnik/eodsql/Update.java (working copy) @@ -104,4 +104,14 @@ */ GeneratedKeys keys() default GeneratedKeys.NO_KEYS_RETURNED; + /** + *

+ * The custom type bindings to be used on the parameters of this query. The binding is matched + * by position. The default type binding will be used for each parameter that has no + * parameterBindings entry or an entry that is of type {@link TypeMapper} itself. + *

+ * + * @since 2.1 + */ + Class[] parameterBindings() default {}; }