From 16c8ec4d3495e87adc93595abc19aa08432ab148 Mon Sep 17 00:00:00 2001 From: bernd Date: Wed, 14 Nov 2012 21:24:31 +0100 Subject: [PATCH 20/25] Add interface DynamicQuery that can be used as a mix-in to add support for dynamically created queries and statements to any query interface. Add interfaces DynamicBaseQuery and DynamicTransactionQuery. --- eodsql/src/net/lemnik/eodsql/DynamicBaseQuery.java | 8 + eodsql/src/net/lemnik/eodsql/DynamicQuery.java | 329 +++++++++++++++++++++ .../net/lemnik/eodsql/DynamicTransactionQuery.java | 10 + eodsql/src/net/lemnik/eodsql/MapQueryParams.java | 26 ++ eodsql/src/net/lemnik/eodsql/QueryParams.java | 90 ++++++ .../src/net/lemnik/eodsql/impl/BaseQueryImpl.java | 240 ++++++++++++++- .../impl/BatchUpdateMethodImplementation.java | 88 +++--- .../eodsql/impl/DynamicParameterFactory.java | 73 +++++ .../eodsql/impl/MethodImplementationKey.java | 69 +++++ .../eodsql/impl/SelectDynamicParameterFactory.java | 196 ++++++++++++ .../eodsql/impl/SelectMethodImplementation.java | 8 +- .../eodsql/impl/UpdateDynamicParameterFactory.java | 145 +++++++++ .../eodsql/impl/UpdateMethodImplementation.java | 21 +- 13 files changed, 1239 insertions(+), 64 deletions(-) create mode 100644 eodsql/src/net/lemnik/eodsql/DynamicBaseQuery.java create mode 100644 eodsql/src/net/lemnik/eodsql/DynamicQuery.java create mode 100644 eodsql/src/net/lemnik/eodsql/DynamicTransactionQuery.java create mode 100644 eodsql/src/net/lemnik/eodsql/MapQueryParams.java create mode 100644 eodsql/src/net/lemnik/eodsql/QueryParams.java create mode 100644 eodsql/src/net/lemnik/eodsql/impl/DynamicParameterFactory.java create mode 100644 eodsql/src/net/lemnik/eodsql/impl/MethodImplementationKey.java create mode 100644 eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java create mode 100644 eodsql/src/net/lemnik/eodsql/impl/UpdateDynamicParameterFactory.java diff --git a/eodsql/src/net/lemnik/eodsql/DynamicBaseQuery.java b/eodsql/src/net/lemnik/eodsql/DynamicBaseQuery.java new file mode 100644 index 0000000..ee8b4ed --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/DynamicBaseQuery.java @@ -0,0 +1,8 @@ +package net.lemnik.eodsql; + +/** + * A version of {@link BaseQuery} that includes the query capabilities of {@link DynamicQuery}. + * + * @author Bernd Rinn + */ +public interface DynamicBaseQuery extends BaseQuery, DynamicQuery { } diff --git a/eodsql/src/net/lemnik/eodsql/DynamicQuery.java b/eodsql/src/net/lemnik/eodsql/DynamicQuery.java new file mode 100644 index 0000000..37e66b8 --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/DynamicQuery.java @@ -0,0 +1,329 @@ +package net.lemnik.eodsql; + +import java.util.List; +import java.util.Map; + +/** + * This interface can be used as a mix-in for SQL queries and statements created + * dynamically at runtime. Usage is like this: + * + *
+ * 
+ * public interface MyQuery extends BaseQuery, DynamicQuery {
+ *     @Select(sql = "SELECT * FROM persons")
+ *     public DataSet<Address> list();
+ * 
+ *     @Select(sql = "SELECT * FROM persons")
+ *     public DataSet<Map<String,Object>> listMap();
+ * }
+ * ...
+ * MyQuery query = QueryTool.getQuery(conn, MyQuery.class);
+ * DataSet<Map<String,Object>> data = query.select("SELECT * FROM persons WHERE last_name = 'Smith'");
+ * 
+ * 
+ *

+ * DynamicQuery can be mixed-in to {@link BaseQuery} and + * {@link TransactionQuery}. The interfaces {@link DynamicBaseQuery} and + * {@link DynamicTransactionQuery} can be used for that. + * + * @author Bernd Rinn + */ +public interface DynamicQuery { + + /** + * Performs a SQL query. The returned DataSet is connected and + * updateable. + * + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result set as DataSet; each row is represented + * as one Map<String,Object>. + */ + DataSet> select(final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query. The returned DataIterator is connected + * and read-only. + * + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result set as DataIterator; each row is + * represented as one Map<String,Object>. + */ + DataIterator> selectIterator(final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query. The returned List is + * independent of the database result set. + * + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result set as List; each row is + * represented as one Map<String,Object>. + */ + List> selectList(final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query that returns zero or one row. It is an error + * condition if the query returns more than one row in its result set. + * + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result row as a Map<String,Object>, or + * null, if no row was found. + */ + Map selectRow(final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query. The returned DataIterator is connected + * and read-only. + * + * @param fetchSize The fetch size hint to give to the statement. + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result set as DataIterator; each row is + * represented as one Map<String,Object>. + */ + DataIterator> selectIterator(final int fetchSize, + final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Performs a SQL query. The returned List is + * independent of the database result set. + * + * @param fetchSize The fetch size hint to give to the statement. + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result set as List; each row is + * represented as one Map<String,Object>. + */ + List> selectList(final int fetchSize, final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query. The returned DataSet is connected and + * updateable. + * + * @param type The Java type to return one rows in the returned + * DataSet. + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result set as DataSet; each row is represented + * as one Map<String,Object>. + */ + DataSet select(final Class type, final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query. The returned DataIterator is connected + * and read-only. + * + * @param type The Java type to return one rows in the returned + * DataIterator. + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result set as DataIterator; each row is + * represented as an object of type T. + */ + DataIterator selectIterator(final Class type, final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query. The returned List is + * independent of the database result set. + * + * @param type The Java type to return one rows in the returned + * List. + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result set as List; each row is + * represented as an object of type T. + */ + List selectList(final Class type, final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query that returns zero or one row. It is an error + * condition if the query returns more than one row in its result set. + * + * @param type The Java type to return one rows in the returned + * DataSet. + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result row as an object of type T, or + * null, if no row was found. + */ + T selectRow(final Class type, final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query. + * + * @param params The parameters of the query. + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result set as DataSet; each row is represented + * as one Map<String,Object>. + */ + DataSet select(final QueryParams params, final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Performs a SQL query. The returned DataIterator is connected + * and read-only. + * + * @param type The Java type to return one rows in the returned + * DataIterator. + * @param fetchSize The fetch size hint to give to the statement. + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result set as DataIterator; each row is + * represented as an object of type T. + */ + DataIterator selectIterator(final Class type, + final int fetchSize, final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Performs a SQL query. The returned List is + * independent of the database result set. + * + * @param type The Java type to return one rows in the returned + * List. + * @param fetchSize The fetch size hint to give to the statement. + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The result set as List; each row is + * represented as an object of type T. + */ + List selectList(final Class type, final int fetchSize, final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Executes a SQL statement. + * + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The number of rows updated by the SQL statement, or -1 if not + * applicable. Note: Not all JDBC drivers support this + * cleanly. + */ + int update(final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Executes a SQL statement as a batch for all parameter values provided. + * + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. At + * least one of the parameters needs to be an array or + * Collection. If multiple parameters are arrays or + * Collection, all of them need to have the same size. + * + * @return The number of rows updated by the SQL statement, or -1 if not + * applicable. Note: Not all JDBC drivers support this + * cleanly. + */ + int batchUpdate(final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Executes a SQL statement. Supposed to be used for INSERT statements with + * an automatically generated integer key. + * + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The automatically generated key. Note: Not all JDBC + * drivers support this cleanly. + */ + long insert(final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Executes a SQL statement. Supposed to be used for INSERT statements with + * one or more automatically generated keys. + * + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. + * + * @return The automatically generated keys. Note: Not all JDBC + * drivers support this cleanly and it is very driver-dependent what + * keys are present in the returned map. + */ + Map insertMultiKeys(final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; + + /** + * Executes a SQL statement as a batch for all parameter values provided. + * Supposed to be used for INSERT statements with an automatically generated + * integer key. + * + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. At + * least one of the parameters needs to be an array or + * Collection. If multiple parameters are arrays or + * Collection, all of them need to have the same size. + * + * @return The automatically generated key for each element of the batch. + * Note: Not all JDBC drivers support this cleanly. + */ + long[] batchInsert(final String query, final Object... parameters) + throws InvalidDataTypeException, InvalidQueryException, + EoDException; + + /** + * Executes a SQL statement as a batch for all parameter values provided. + * Supposed to be used for INSERT statements with one or more automatically + * generated keys. + * + * @param query The SQL query template. + * @param parameters The parameters to fill into the SQL query template. At + * least one of the parameters needs to be an array or + * Collection. If multiple parameters are arrays or + * Collection, all of them need to have the same size. + * + * @return The automatically generated keys for each element of the batch. + * Note: Not all JDBC drivers support this cleanly and it is + * very driver-dependent what keys are present in the returned map. + */ + Map[] batchInsertMultiKeys(final String query, + final Object... parameters) throws InvalidDataTypeException, + InvalidQueryException, EoDException; +} diff --git a/eodsql/src/net/lemnik/eodsql/DynamicTransactionQuery.java b/eodsql/src/net/lemnik/eodsql/DynamicTransactionQuery.java new file mode 100644 index 0000000..73ec8d8 --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/DynamicTransactionQuery.java @@ -0,0 +1,10 @@ +package net.lemnik.eodsql; + +/** + * A version of {@link TransactionQuery} that includes the query capabilities of + * {@link DynamicQuery}. + * + * @author Bernd Rinn + */ +public interface DynamicTransactionQuery extends TransactionQuery, DynamicQuery { +} diff --git a/eodsql/src/net/lemnik/eodsql/MapQueryParams.java b/eodsql/src/net/lemnik/eodsql/MapQueryParams.java new file mode 100644 index 0000000..91fbb08 --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/MapQueryParams.java @@ -0,0 +1,26 @@ +package net.lemnik.eodsql; + +import java.util.Map; + +/** + * A {@link QueryParams} object for Map<String,Object> result types. + * + * @author Bernd Rinn + */ +public class MapQueryParams extends QueryParams> { + + public MapQueryParams() { + } + + @Override + @SuppressWarnings("unchecked") + public Class> getBaseReturnType() { + // This kludge by courtesy of the beautiful Java type system. + return (Class>) getMapBaseReturnType(); + } + + private Class getMapBaseReturnType() { + return Map.class; + } + +} diff --git a/eodsql/src/net/lemnik/eodsql/QueryParams.java b/eodsql/src/net/lemnik/eodsql/QueryParams.java new file mode 100644 index 0000000..ba2a540 --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/QueryParams.java @@ -0,0 +1,90 @@ +package net.lemnik.eodsql; + +/** + * Class to encode the query parameters used in a {@link DynamicQuery}. + * + * @author Bernd Rinn + */ +public class QueryParams { + + protected Class baseReturnType; + + private boolean readOnly; + + private boolean disconnected; + + private int fetchSize; + + protected QueryParams() { + } + + /** + * Creates a new {@QueryParams} object. + * + * @param baseReturnType The return type that represents a single row in the query result. + */ + public QueryParams(Class baseReturnType) { + this.baseReturnType = baseReturnType; + } + + /** + * Sets whether the returned {@link DataSet} will be read-only (default is + * false). + *

+ * This has no effect on queries that return a {@link DataIterator} or + * {@link java.util.List} or a single row as they are always read-only. + * + * @return This object, for chaining calls. + */ + public QueryParams readOnly(boolean readOnly) { + this.readOnly = readOnly; + return this; + } + + /** + * Sets whether the returned {@link DataSet} will be disconnected (default + * is false). + *

+ * This has no effect on queries that return a {@link DataIterator} or + * {@link java.util.List} or a single row. {@link DataIterator}s are always + * connected, {@link java.util.List} and single row results are always + * disconnected. + * + * @return This object, for chaining calls. + */ + public QueryParams disconnected(boolean disconnected) { + this.disconnected = disconnected; + if (disconnected) { + // Disconnected datasets may not be updateable. + this.readOnly = true; + } + return this; + } + + /** + * Sets a fetch size for the query. The default is 0 which leaves the decision to the JDBC driver. + * + * @return This object, for chaining calls. + */ + public QueryParams fetchSize(int fetchSize) { + this.fetchSize = fetchSize; + return this; + } + + public Class getBaseReturnType() { + return baseReturnType; + } + + public boolean isReadOnly() { + return readOnly; + } + + public boolean isDisconnected() { + return disconnected; + } + + public int getFetchSize() { + return fetchSize; + } + +} diff --git a/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java b/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java index 6b56cdf..59bf7e8 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java +++ b/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java @@ -1,35 +1,37 @@ package net.lemnik.eodsql.impl; import java.lang.annotation.Annotation; - -import java.lang.reflect.Method; import java.lang.reflect.InvocationHandler; - +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.sql.Connection; import java.sql.SQLException; - +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.Map; import java.util.Set; -import java.util.HashSet; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Collections; - import java.util.concurrent.locks.ReentrantLock; import javax.sql.DataSource; import net.lemnik.eodsql.BaseQuery; +import net.lemnik.eodsql.DynamicQuery; import net.lemnik.eodsql.EoDException; -import net.lemnik.eodsql.QueryTool; +import net.lemnik.eodsql.GeneratedKeys; import net.lemnik.eodsql.InvalidQueryException; - +import net.lemnik.eodsql.QueryParams; +import net.lemnik.eodsql.QueryTool; +import net.lemnik.eodsql.impl.DynamicParameterFactory.ReturnType; +import net.lemnik.eodsql.impl.SelectDynamicParameterFactory.CallSchema; +import net.lemnik.eodsql.spi.Context; import net.lemnik.eodsql.spi.MethodImplementation; import net.lemnik.eodsql.spi.MethodImplementationFactory; - -import net.lemnik.eodsql.spi.Context; import net.lemnik.eodsql.spi.Resource; +import net.lemnik.eodsql.spi.util.MapDataObjectBinding; /** * Created on 2008/06/26 @@ -37,9 +39,12 @@ import net.lemnik.eodsql.spi.Resource; */ class BaseQueryImpl implements InvocationHandler { - protected Map methods = new HashMap(); + protected final Map methods = new HashMap(); + + protected final Map> + dynamicMethodImplCache = new HashMap>(); - protected ConnectionSource connectionSource; + protected final ConnectionSource connectionSource; BaseQueryImpl( final ConnectionSource connectionSource, @@ -121,6 +126,10 @@ class BaseQueryImpl implements InvocationHandler { final Class baseInterface) { final Set parents = getParentInterfaces(baseInterface); + if (DynamicQuery.class.isAssignableFrom(clazz)) { + parents.add(DynamicQuery.class); + addDynamicQueryMethods(); + } for(final Method method : clazz.getMethods()) { if(parents.contains(method.getDeclaringClass())) { @@ -138,7 +147,129 @@ class BaseQueryImpl implements InvocationHandler { } catch(SecurityException securityException) { } } - + + private void addDynamicQueryMethods() { + try { + methods.put(DynamicQuery.class.getMethod("select", String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.SIMPLE, ReturnType.DATASET) + ) + ); + methods.put(DynamicQuery.class.getMethod("selectIterator", String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.SIMPLE, ReturnType.DATAITERATOR) + ) + ); + methods.put(DynamicQuery.class.getMethod("selectList", String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.SIMPLE, ReturnType.LIST) + ) + ); + methods.put(DynamicQuery.class.getMethod("selectRow", String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.SIMPLE, ReturnType.SINGLEROW) + ) + ); + methods.put(DynamicQuery.class.getMethod("selectIterator", Integer.TYPE, + String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.FETCHSIZE, ReturnType.DATAITERATOR) + ) + ); + methods.put(DynamicQuery.class.getMethod("selectList", Integer.TYPE, + String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.FETCHSIZE, ReturnType.LIST) + ) + ); + methods.put(DynamicQuery.class.getMethod("select", Class.class, String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.BASERETURNTYPE, ReturnType.DATASET) + ) + ); + methods.put(DynamicQuery.class.getMethod("selectIterator", Class.class, String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.BASERETURNTYPE, ReturnType.DATAITERATOR) + ) + ); + methods.put(DynamicQuery.class.getMethod("selectList", Class.class, String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.BASERETURNTYPE, ReturnType.LIST) + ) + ); + methods.put(DynamicQuery.class.getMethod("selectRow", Class.class, String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.BASERETURNTYPE, ReturnType.SINGLEROW) + ) + ); + methods.put(DynamicQuery.class.getMethod("select", QueryParams.class, String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.QUERYPARAMS, ReturnType.DATASET) + ) + ); + methods.put(DynamicQuery.class.getMethod("selectIterator", Class.class, Integer.TYPE, + String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.BASERETURNTYPE_FETCHSIZE, ReturnType.DATAITERATOR) + ) + ); + methods.put(DynamicQuery.class.getMethod("selectList", Class.class, Integer.TYPE, + String.class, Object[].class), + new DynamicMethodImpl( + new SelectDynamicParameterFactory( + CallSchema.BASERETURNTYPE_FETCHSIZE, ReturnType.LIST) + ) + ); + + methods.put(DynamicQuery.class.getMethod("update", String.class, Object[].class), + new DynamicMethodImpl( + new UpdateDynamicParameterFactory( + Integer.TYPE, -1, true, 0, false, GeneratedKeys.NO_KEYS_RETURNED)) + ); + methods.put(DynamicQuery.class.getMethod("batchUpdate", String.class, Object[].class), + new DynamicMethodImpl( + new UpdateDynamicParameterFactory( + Integer.TYPE, -1, true, 0, true, GeneratedKeys.NO_KEYS_RETURNED)) + ); + + methods.put(DynamicQuery.class.getMethod("insert", String.class, Object[].class), + new DynamicMethodImpl( + new UpdateDynamicParameterFactory( + Long.TYPE, -1, true, 0, false, GeneratedKeys.RETURNED_KEYS_FIRST_COLUMN)) + ); + methods.put(DynamicQuery.class.getMethod("insertMultiKeys", String.class, Object[].class), + new DynamicMethodImpl( + new UpdateDynamicParameterFactory( + MapDataObjectBinding.getStringObjectMapObjectType(), -1, true, 0, false, + GeneratedKeys.RETURNED_KEYS_DRIVER_DEFINED)) + ); + methods.put(DynamicQuery.class.getMethod("batchInsert", String.class, Object[].class), + new DynamicMethodImpl( + new UpdateDynamicParameterFactory( + long[].class, -1, true, 0, true, GeneratedKeys.RETURNED_KEYS_FIRST_COLUMN)) + ); + methods.put(DynamicQuery.class.getMethod("batchInsertMultiKeys", String.class, Object[].class), + new DynamicMethodImpl( + new UpdateDynamicParameterFactory( + Map[].class, -1, true, 0, true, GeneratedKeys.RETURNED_KEYS_DRIVER_DEFINED)) + ); + } catch(NoSuchMethodException noSuchMethodException) { + } catch(SecurityException securityException) { + } + } + public Object invoke( final Object proxy, final Method method, @@ -237,6 +368,83 @@ class BaseQueryImpl implements InvocationHandler { } } + class DynamicMethodImpl implements SQLCallable { + + private final DynamicParameterFactory factory; + + public DynamicMethodImpl(final DynamicParameterFactory factory) { + this.factory = factory; + } + + private Type createReturnTypeDataSet(final Class baseReturnType) { + if (factory.getReturnType().isSingleRow()) { + return baseReturnType; + } else { + return new ParameterizedType() { + private final Class[] typeArgs = new Class[] { baseReturnType }; + + public Type[] getActualTypeArguments() { + return typeArgs; + } + + public Type getRawType() { + return factory.getReturnType().getRawType(); + } + + public Type getOwnerType() { + return null; + } + }; + } + } + + @SuppressWarnings("unchecked") + public Object invoke( + final Method method, + final Object[] args) + throws Throwable { + + final Annotation annotation = factory.createAnnotation(args); + final Object[] queryParams = (Object[]) args[args.length - 1]; + final Context context = createContext(annotation, method.getReturnType(), queryParams); + + final Resource connection = + new ConnectionSourceConnectionResource(connectionSource); + + context.setResource(connection); + + try { + final MethodImplementationKey key = new MethodImplementationKey(method, annotation, + getQueryParamTypes(queryParams), factory.createBaseReturnType(args)); + MethodImplementation impl = dynamicMethodImplCache.get(key); + if (impl == null) { + impl = factory.createMethodImplementation(key.method, key.annotation, + key.queryParamTypes, createReturnTypeDataSet(key.baseReturnType), args); + dynamicMethodImplCache.put(key, impl); + } + impl.invoke(context); + return context.getReturnValue(); + } finally { + if(context.isAutoclose()) { + context.close(); + } + } + } + + private Class[] getQueryParamTypes(final Object[] queryParams) { + final Class[] queryParamTypes = new Class[queryParams.length]; + for (int i = 0; i < queryParamTypes.length; i++) { + queryParamTypes[i] = queryParams[i].getClass(); + } + return queryParamTypes; + } + + public String getSQL(Method method, Object[] args) { + return factory.getSql(args); + } + + } + static interface ConnectionSource { public Connection getConnection() throws SQLException; diff --git a/eodsql/src/net/lemnik/eodsql/impl/BatchUpdateMethodImplementation.java b/eodsql/src/net/lemnik/eodsql/impl/BatchUpdateMethodImplementation.java index 05c8e32..85ebca4 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/BatchUpdateMethodImplementation.java +++ b/eodsql/src/net/lemnik/eodsql/impl/BatchUpdateMethodImplementation.java @@ -25,7 +25,7 @@ import net.lemnik.eodsql.spi.Context; /** * Implementation of {@link Update} for batch updates. - * + * * @author Bernd Rinn */ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { @@ -36,7 +36,20 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { BatchUpdateMethodImplementation(final Method method) throws ParseException { super(method); - viewFactories = createParameterViewFactories(method); + viewFactories = createParameterViewFactories(method.getParameterTypes()); + firstIndexOfFiniteCollection = findFirstIndexOfFiniteCollection(viewFactories); + if (firstIndexOfFiniteCollection == -1) { + throw new RuntimeException("Method '" + method.getDeclaringClass().getSimpleName() + + "." + method.getName() + + "' supposed to do batch update, but has no batch parameter."); + } + } + + BatchUpdateMethodImplementation(final Update update, + final Class[] parameterTypes, final Class[] elementTypes, + final Type returnType, final Method method) throws ParseException { + super(update, elementTypes, returnType); + viewFactories = createParameterViewFactories(parameterTypes); firstIndexOfFiniteCollection = findFirstIndexOfFiniteCollection(viewFactories); if (firstIndexOfFiniteCollection == -1) { throw new RuntimeException("Method '" + method.getDeclaringClass().getSimpleName() @@ -51,14 +64,14 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { int size = -1; - for(int i = 0; i < parameterCount; i++) { + for (int i = 0; i < parameterCount; i++) { final Collection v = viewFactories[i].createView(parameters[i]); final int parameterSize = v.size(); - if(parameterSize != -1) { - if(size == -1) { + if (parameterSize != -1) { + if (size == -1) { size = parameterSize; - } else if(size != parameterSize) { + } else if (size != parameterSize) { throw new EoDException("Batch parameter is mismatched " + "in size: " + i); } @@ -73,8 +86,8 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { private Iterator> iterate(final Context ctx) { final Iterator[] views = createParameterViews(ctx.getParameters()); - if(views.length == 0) { - return Collections.>emptyList().iterator(); + if (views.length == 0) { + return Collections.> emptyList().iterator(); } else { return new Iterator>() { @@ -95,7 +108,7 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { } public Context next() { - for(int i = 0; i < paramCount; i++) { + for (int i = 0; i < paramCount; i++) { parameters[i] = views[i].next(); } @@ -113,9 +126,9 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { private static ParameterViewFactory createParameterViewFactory( final Class type) { - if(type.isArray()) { + if (type.isArray()) { return new ArrayViewFactory(); - } else if(Collection.class.isAssignableFrom(type)) { + } else if (Collection.class.isAssignableFrom(type)) { return new CollectionViewFactory(); } else { return new SimpleParameterIteratorFactory(); @@ -123,14 +136,13 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { } static ParameterViewFactory[] createParameterViewFactories( - final Method method) { + final Class[] parameterTypes) { - final Class[] parameterTypes = method.getParameterTypes(); final int parameterCount = parameterTypes.length; final ParameterViewFactory[] factories = new ParameterViewFactory[parameterCount]; - for(int i = 0; i < parameterCount; i++) { + for (int i = 0; i < parameterCount; i++) { factories[i] = createParameterViewFactory(parameterTypes[i]); } @@ -154,32 +166,32 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { final int parameterCount = genericTypes.length; final Class[] classes = new Class[parameterCount]; - for(int i = 0; i < parameterCount; i++) { + for (int i = 0; i < parameterCount; i++) { final Type type = genericTypes[i]; Class clazz = null; - if(type instanceof Class) { - clazz = (Class)type; + if (type instanceof Class) { + clazz = (Class) type; - if(clazz.isArray()) { + if (clazz.isArray()) { clazz = clazz.getComponentType(); } - } else if(type instanceof ParameterizedType) { - final ParameterizedType ptype = (ParameterizedType)type; + } else if (type instanceof ParameterizedType) { + final ParameterizedType ptype = (ParameterizedType) type; - if(ptype.getRawType() instanceof Class) { - final Class raw = (Class)ptype.getRawType(); + if (ptype.getRawType() instanceof Class) { + final Class raw = (Class) ptype.getRawType(); - if(Iterable.class.isAssignableFrom(raw)) { + if (Iterable.class.isAssignableFrom(raw)) { final Type[] args = ptype.getActualTypeArguments(); - if(args.length != 1) { + if (args.length != 1) { throw new InvalidQueryException( "A generic Iterable may only have " + "one type argument: " + ptype, method); - } else if(args[0] instanceof Class) { - clazz = (Class)args[0]; + } else if (args[0] instanceof Class) { + clazz = (Class) args[0]; } else { throw new InvalidQueryException( "A generic Iterable must have a " + @@ -188,8 +200,8 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { } } else { clazz = raw; - - if(clazz.isArray()) { + + if (clazz.isArray()) { clazz = clazz.getComponentType(); } } @@ -199,12 +211,12 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { "class type: " + type, method); } - } else if(type instanceof GenericArrayType) { - final GenericArrayType gtype = (GenericArrayType)type; + } else if (type instanceof GenericArrayType) { + final GenericArrayType gtype = (GenericArrayType) type; final Type component = gtype.getGenericComponentType(); - if(component instanceof Class) { - clazz = (Class)component; + if (component instanceof Class) { + clazz = (Class) component; } else { throw new InvalidQueryException( "Arrays must have a concrete " + @@ -231,7 +243,7 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { final Iterator> iterator = iterate(context); - while(iterator.hasNext()) { + while (iterator.hasNext()) { final Context ctx = iterator.next(); fillPreparedStatementParameters(ctx, statement); @@ -245,7 +257,7 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { * Factory for creating an iterator for a batch update parameter. This iterator * will deliver all the values that will be used for that parameter in the batch * update. - * + * * @author Bernd Rinn */ interface ParameterViewFactory { @@ -254,9 +266,9 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { /** * Creates an iterator for the given batch update parameter. - * + * * @param parameter The parameter of the batch update method to get the iterator for. - * + * * @return a new iterator for the parameter. */ Collection createView(Object parameter); @@ -297,7 +309,7 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { } public Object next() { - if(!hasNext()) { + if (!hasNext()) { throw new NoSuchElementException(); } @@ -322,7 +334,7 @@ class BatchUpdateMethodImplementation extends UpdateMethodImplementation { private static class CollectionViewFactory implements ParameterViewFactory { public Collection createView(final Object parameter) { - return ((Collection)parameter); + return ((Collection) parameter); } public boolean isFiniteCollection() diff --git a/eodsql/src/net/lemnik/eodsql/impl/DynamicParameterFactory.java b/eodsql/src/net/lemnik/eodsql/impl/DynamicParameterFactory.java new file mode 100644 index 0000000..10dddf3 --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/impl/DynamicParameterFactory.java @@ -0,0 +1,73 @@ +package net.lemnik.eodsql.impl; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.text.ParseException; +import java.util.List; + +import net.lemnik.eodsql.DataIterator; +import net.lemnik.eodsql.DataSet; +import net.lemnik.eodsql.spi.MethodImplementation; + +/** + * A factory for query method implementation parameters from dynamic invocation arguments. + * + * @author Bernd Rinn + */ +interface DynamicParameterFactory { + + enum ReturnType { + DATASET(DataSet.class, false), + DATAITERATOR(DataIterator.class, false), + LIST(List.class, false), + SINGLEROW(null, true); + + private final Class rawType; + + private final boolean singleRow; + + ReturnType(Class rawType, boolean singleRow) { + this.rawType = rawType; + this.singleRow = singleRow; + } + + Class getRawType() { + return rawType; + } + + boolean isSingleRow() { + return singleRow; + } + } + + /** + * Creates the annotation dynamically for the given method arguments. + */ + Annotation createAnnotation(final Object[] args); + + /** + * Creates the base return type (e.g. the record type) for the given method arguments. + */ + Class createBaseReturnType(final Object[] args); + + /** + * Creates the implementation to invoke for the dynamic query. + */ + MethodImplementation createMethodImplementation(Method method, + Annotation annotation, Class[] queryParamTypes, Type returnType, Object[] args) + throws ParseException; + + /** + * Returns the return type of the query + *

+ * Can be either a single row type, a collection, a data set or a data iterator. + */ + ReturnType getReturnType(); + + /** + * Returns the SQL to execute for the given method arguments. + */ + String getSql(Object[] args); + +} \ No newline at end of file diff --git a/eodsql/src/net/lemnik/eodsql/impl/MethodImplementationKey.java b/eodsql/src/net/lemnik/eodsql/impl/MethodImplementationKey.java new file mode 100644 index 0000000..0d569ba --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/impl/MethodImplementationKey.java @@ -0,0 +1,69 @@ +package net.lemnik.eodsql.impl; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * A key class of method implementations used for caching. + * + * @author Bernd Rinn + */ +class MethodImplementationKey { + final Method method; + final Annotation annotation; + final Class[] queryParamTypes; + final Class baseReturnType; + + MethodImplementationKey(Method method, + Annotation annotation, Class[] queryParamTypes, Class baseReturnType) { + this.method = method; + this.annotation = annotation; + this.queryParamTypes = queryParamTypes; + this.baseReturnType = baseReturnType; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((annotation == null) ? 0 : annotation.hashCode()); + result = prime * result + + ((method == null) ? 0 : method.hashCode()); + result = prime * result + Arrays.hashCode(queryParamTypes); + result = prime * result + + ((baseReturnType == null) ? 0 : baseReturnType.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + MethodImplementationKey other = (MethodImplementationKey) obj; + if (annotation == null) { + if (other.annotation != null) + return false; + } else if (!annotation.equals(other.annotation)) + return false; + if (method == null) { + if (other.method != null) + return false; + } else if (!method.equals(other.method)) + return false; + if (!Arrays.equals(queryParamTypes, other.queryParamTypes)) + return false; + if (baseReturnType == null) { + if (other.baseReturnType != null) + return false; + } else if (!baseReturnType.equals(other.baseReturnType)) + return false; + return true; + } + +} \ No newline at end of file diff --git a/eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java b/eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java new file mode 100644 index 0000000..7fa128f --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java @@ -0,0 +1,196 @@ +package net.lemnik.eodsql.impl; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.text.ParseException; + +import net.lemnik.eodsql.ArrayDataSetCache; +import net.lemnik.eodsql.DataSetCache; +import net.lemnik.eodsql.QueryParams; +import net.lemnik.eodsql.Select; +import net.lemnik.eodsql.TypeMapper; +import net.lemnik.eodsql.spi.MethodImplementation; +import net.lemnik.eodsql.spi.util.DataObjectBinding; +import net.lemnik.eodsql.spi.util.MapDataObjectBinding; +import net.lemnik.eodsql.spi.util.NoDataObjectBinding; + +/** + * A {@link DynamicParameterFactory} for SELECT query method invocations. + * + * @author Bernd Rinn + */ +class SelectDynamicParameterFactory implements DynamicParameterFactory { + + enum CallSchema { + SIMPLE(false, -1, -1, 0), + BASERETURNTYPE(false, 0, -1, 1), + FETCHSIZE(false, -1, 0, 1), + BASERETURNTYPE_FETCHSIZE(false, 0, 1, 2), + QUERYPARAMS(true, -1, -1, 1); + + private final boolean useQueryParams; + + private final int baseReturnTypePos; + + private final int fetchSizePos; + + private final int sqlPos; + + CallSchema(boolean useQueryParams, int baseReturnTypePos, int fetchSizePos, + int sqlPos) { + this.useQueryParams = useQueryParams; + this.baseReturnTypePos = baseReturnTypePos; + this.fetchSizePos = fetchSizePos; + this.sqlPos = sqlPos; + } + + private QueryParams getQueryParams(final Object[] args) { + return (QueryParams) args[0]; + } + + Class getBaseReturnType(Object[] args) { + if (useQueryParams) { + return getQueryParams(args).getBaseReturnType(); + } else if (baseReturnTypePos >= 0) + { + return (Class) args[baseReturnTypePos]; + } else { + return MapDataObjectBinding.getStringObjectMapObjectType(); + } + } + + int getFetchSize(Object[] args) { + if (useQueryParams) { + return getQueryParams(args).getFetchSize(); + } else if (fetchSizePos >= 0) { + return (Integer) args[fetchSizePos]; + } else { + return 0; + } + } + + boolean isDisconnected(Object[] args) { + if (useQueryParams) { + return getQueryParams(args).isDisconnected(); + } else { + return false; + } + } + + boolean isReadOnly(Object[] args) { + if (useQueryParams) { + return getQueryParams(args).isReadOnly(); + } else { + return false; + } + } + + String getSql(Object[] args) { + return (String) args[sqlPos]; + } + + } + + private final CallSchema callSchema; + + private final ReturnType returnType; + + SelectDynamicParameterFactory(final CallSchema callSchema, final ReturnType returnType) { + this.callSchema = callSchema; + this.returnType = returnType; + } + + public Annotation createAnnotation(final Object[] args) { + return new Select() { + + public String value() { + return ""; + } + + public String sql() { + return getSql(args); + } + + public boolean disconnected() { + return callSchema.isDisconnected(args); + } + + public boolean rubberstamp() { + return false; + } + + public boolean readOnly() { + return callSchema.isReadOnly(args); + } + + public Class cache() { + return ArrayDataSetCache.class; + } + + public Class annotationType() { + return Select.class; + } + + public int fetchSize() { + return callSchema.getFetchSize(args); + } + + public int into() { + return 0; + } + + public Class resultSetBinding() { + return NoDataObjectBinding.class; + } + + @SuppressWarnings("unchecked") + public Class[] parameterBindings() { + return new Class[0]; + } + + @Override + public int hashCode() { + int hash = "Select".hashCode(); + hash = 37 * hash + sql().hashCode(); + hash = 37 * hash + (disconnected() ? 1 : 0); + hash = 37 * hash + (readOnly() ? 1 : 0); + hash = 37 * hash + fetchSize(); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Select == false) { + return false; + } + Select other = (Select) obj; + return sql().equals(other.sql()) + && disconnected() == other.disconnected() + && readOnly() == other.readOnly() + && fetchSize() == other.fetchSize(); + } + }; + + } + + public String getSql(Object[] args) { + return callSchema.getSql(args); + } + + public MethodImplementation createMethodImplementation( + Method method, Annotation annotation, Class[] queryParamTypes, + Type returnType, Object[] args) throws ParseException { + return new SelectMethodImplementation((Select) annotation, + queryParamTypes, returnType); + } + + public Class createBaseReturnType(final Object[] args) { + return callSchema.getBaseReturnType(args); + } + + public ReturnType getReturnType() { + return returnType; + } + +} \ No newline at end of file diff --git a/eodsql/src/net/lemnik/eodsql/impl/SelectMethodImplementation.java b/eodsql/src/net/lemnik/eodsql/impl/SelectMethodImplementation.java index 86f7917..c6589bb 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/SelectMethodImplementation.java +++ b/eodsql/src/net/lemnik/eodsql/impl/SelectMethodImplementation.java @@ -37,22 +37,24 @@ class SelectMethodImplementation extends AbstractMethodImplementation