diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/Call.java --- a/src/net/lemnik/eodsql/Call.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/Call.java Sat Sep 18 23:31:37 2010 +0200 @@ -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.2 + */ + 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.2 + */ + Class[] parameterBindings() default {}; } diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/QuickQueryUtil.java --- a/src/net/lemnik/eodsql/QuickQueryUtil.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/QuickQueryUtil.java Sat Sep 18 23:31:37 2010 +0200 @@ -20,6 +20,8 @@ import net.lemnik.eodsql.spi.ResultSetResource; import net.lemnik.eodsql.spi.StatementResource; +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; @@ -74,6 +76,16 @@ return 0; } + public Class resultSetBinding() + { + return NoDataObjectBinding.class; + } + + public Class[] parameterBindings() + { + return new Class[0]; + } + }; private QuickQueryUtil() { diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/Select.java --- a/src/net/lemnik/eodsql/Select.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/Select.java Sat Sep 18 23:31:37 2010 +0200 @@ -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; /** *

@@ -232,4 +234,24 @@ */ int into() 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.2 + */ + 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.2 + */ + Class resultSetBinding() default NoDataObjectBinding.class; } diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/Update.java --- a/src/net/lemnik/eodsql/Update.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/Update.java Sat Sep 18 23:31:37 2010 +0200 @@ -111,4 +111,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.2 + */ + Class[] parameterBindings() default {}; } diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java --- a/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java Sat Sep 18 23:31:37 2010 +0200 @@ -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; @@ -69,31 +71,63 @@ 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 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 { final TypeMapper mapper = knownMappers.get(query.getParameterType(i)); if(mapper != null) { mappers[i] = mapper; } else { - throw new InvalidQueryException( - "Unknown primitive type: " + + 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); + } + } + } diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/impl/CallMethodImplementation.java --- a/src/net/lemnik/eodsql/impl/CallMethodImplementation.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/impl/CallMethodImplementation.java Sat Sep 18 23:31:37 2010 +0200 @@ -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; diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/impl/SelectMethodImplementation.java --- a/src/net/lemnik/eodsql/impl/SelectMethodImplementation.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/impl/SelectMethodImplementation.java Sat Sep 18 23:31:37 2010 +0200 @@ -21,6 +21,7 @@ import net.lemnik.eodsql.spi.MethodImplementationFactory; import net.lemnik.eodsql.spi.ResultSetResource; +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; @@ -46,6 +47,8 @@ query = Query.getQuery(queryString, method.getParameterTypes()); + setParameterMappers(select.parameterBindings()); + final Map parameters = extractReturnTypeMapperParameters(select); @@ -89,6 +92,17 @@ 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()); return parameters; diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/impl/UpdateMethodImplementation.java --- a/src/net/lemnik/eodsql/impl/UpdateMethodImplementation.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/impl/UpdateMethodImplementation.java Sat Sep 18 23:31:37 2010 +0200 @@ -49,6 +49,8 @@ query = Query.getQuery(queryString, getParameterTypes(method)); + setParameterMappers(update.parameterBindings()); + final Map parameters = extractReturnTypeMapperParameters(update); diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/spi/util/ArrayWrapper.java --- a/src/net/lemnik/eodsql/spi/util/ArrayWrapper.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/spi/util/ArrayWrapper.java Sat Sep 18 23:31:37 2010 +0200 @@ -226,9 +226,16 @@ final Class arrayType = (Class)genericType; + 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)); } + } } } diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/spi/util/CollectionWrapperFactory.java --- a/src/net/lemnik/eodsql/spi/util/CollectionWrapperFactory.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/spi/util/CollectionWrapperFactory.java Sat Sep 18 23:31:37 2010 +0200 @@ -27,6 +27,7 @@ import net.lemnik.eodsql.spi.util.DataObjectBinding.BindingType; import static net.lemnik.eodsql.spi.util.ResultSetWrapper.PARAMETER_BINDING_TYPE; +import static net.lemnik.eodsql.spi.util.ResultSetWrapper.PARAMETER_CUSTOM_DATA_OBJECT_BINDING; /** *

@@ -174,9 +175,15 @@ final Class dataObject = (Class)ptype.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); + } if(clazz.equals(Collection.class) || clazz.equals(List.class)) { return new ListWrapper(binding); diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/spi/util/DataIteratorWrapper.java --- a/src/net/lemnik/eodsql/spi/util/DataIteratorWrapper.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/spi/util/DataIteratorWrapper.java Sat Sep 18 23:31:37 2010 +0200 @@ -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( + 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); } diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/spi/util/DataObjectBinding.java --- a/src/net/lemnik/eodsql/spi/util/DataObjectBinding.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/spi/util/DataObjectBinding.java Sat Sep 18 23:31:37 2010 +0200 @@ -407,6 +407,9 @@ @SuppressWarnings(value = "unchecked") final DataObjectBinding binding = (DataObjectBinding)bindingClass.newInstance(); + if (binding.getObjectType() == null) { + binding.setObjectType(dataObject); + } return trySetBindingType(binding, bindingType); } catch(final InstantiationException instantiationException) { throw new EoDException(instantiationException); diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/spi/util/DataSetWrapper.java --- a/src/net/lemnik/eodsql/spi/util/DataSetWrapper.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/spi/util/DataSetWrapper.java Sat Sep 18 23:31:37 2010 +0200 @@ -89,8 +89,15 @@ final Class dataObjectType, final Map 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); diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/spi/util/NoDataObjectBinding.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/lemnik/eodsql/spi/util/NoDataObjectBinding.java Sat Sep 18 23:31:37 2010 +0200 @@ -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(); + } +} diff -r 718bb89ca52e -r 18df2bf882ee src/net/lemnik/eodsql/spi/util/ResultSetWrapper.java --- a/src/net/lemnik/eodsql/spi/util/ResultSetWrapper.java Sat Sep 18 23:25:53 2010 +0200 +++ b/src/net/lemnik/eodsql/spi/util/ResultSetWrapper.java Sat Sep 18 23:31:37 2010 +0200 @@ -62,6 +62,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 = @@ -111,17 +119,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 +196,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 +381,8 @@ return getResultSetWrapperForSimpleClass( type, - genericReturnType, - getBindingType(parameters)); + getBindingType(parameters), + getCustomBinding(parameters)); } else { for(final Factory f : FACTORIES) { if(f.isTypeConstructable(genericReturnType, parameters)) {