From ccf08d237eacc6e006883d38af1c11442599e90f Mon Sep 17 00:00:00 2001 From: bernd Date: Wed, 14 Nov 2012 21:24:31 +0100 Subject: [PATCH 21/25] Add support for managed connections and an example on how to use then with Spring transaction managers. --- .../net/lemnik/eodsql/mock/MockQueryFactory.java | 5 + eodsql/build.properties | 1 + .../spring/SpringEoDSQLTransactionManager.java | 50 +++ .../src/net/lemnik/eodsql/QueryFactoryChain.java | 62 ++++ eodsql/src/net/lemnik/eodsql/QueryTool.java | 341 ++++++++++++++++++++- .../src/net/lemnik/eodsql/impl/BaseQueryImpl.java | 29 ++ .../lemnik/eodsql/impl/DefaultQueryFactory.java | 14 + .../net/lemnik/eodsql/impl/ManagedConnections.java | 68 ++++ eodsql/src/net/lemnik/eodsql/spi/QueryFactory.java | 18 ++ eodsql/test/net/lemnik/eodsql/EoDTestCase.java | 17 + .../net/lemnik/eodsql/ManagedConnectionTest.java | 113 +++++++ 11 files changed, 717 insertions(+), 1 deletion(-) create mode 100644 eodsql/docs/documentation/examples/net/lemnik/eodsql/spring/SpringEoDSQLTransactionManager.java create mode 100644 eodsql/src/net/lemnik/eodsql/impl/ManagedConnections.java create mode 100644 eodsql/test/net/lemnik/eodsql/ManagedConnectionTest.java diff --git a/eodsql/addon/eod-mock/src/net/lemnik/eodsql/mock/MockQueryFactory.java b/eodsql/addon/eod-mock/src/net/lemnik/eodsql/mock/MockQueryFactory.java index 94dc50f..5722ba6 100644 --- a/eodsql/addon/eod-mock/src/net/lemnik/eodsql/mock/MockQueryFactory.java +++ b/eodsql/addon/eod-mock/src/net/lemnik/eodsql/mock/MockQueryFactory.java @@ -90,4 +90,9 @@ public class MockQueryFactory implements QueryFactory { } } + public T construct(String databaseName, Class query, + ClassLoader loader) throws InvalidQueryException { + throw new UnsupportedOperationException("Not supported yet."); + } + } diff --git a/eodsql/build.properties b/eodsql/build.properties index f583ee4..802dc67 100644 --- a/eodsql/build.properties +++ b/eodsql/build.properties @@ -12,5 +12,6 @@ version=2.2 test.db.driver=org.hsqldb.jdbcDriver test.db.url=jdbc:hsqldb:mem:testdb +test.db.url2=jdbc:hsqldb:mem:testdb2 test.db.user=sa test.db.password= diff --git a/eodsql/docs/documentation/examples/net/lemnik/eodsql/spring/SpringEoDSQLTransactionManager.java b/eodsql/docs/documentation/examples/net/lemnik/eodsql/spring/SpringEoDSQLTransactionManager.java new file mode 100644 index 0000000..ca4f476 --- /dev/null +++ b/eodsql/docs/documentation/examples/net/lemnik/eodsql/spring/SpringEoDSQLTransactionManager.java @@ -0,0 +1,50 @@ +package net.lemnik.eodsql.spring; +import net.lemnik.eodsql.QueryTool; + +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport; +import org.springframework.transaction.TransactionDefinition; + +/** + * A sub-class of Spring's {@link DataSourceTransactionManager} which sets the stage for using + * managed database connections with EoDSQL. + *

+ * To use it, put something like this in your applicationContext.xml: + *

+ * + *

+ * <bean id="transactionManager" 
+ *         class="net.lemnik.eodsql.spring.SpringEoDSQLTransactionManager"
+ *         <constructor-arg ref="data-source" />
+ * </bean>
+ * <bean id="transactionInterceptor"
+ *         class="org.springframework.transaction.interceptor.TransactionInterceptor">
+ *         <property name="transactionManager" ref="transactionManager" />
+ *         <property name="transactionAttributeSource">
+ *             <bean
+ *                 class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource" />
+ *         </property>
+ * </bean>
+ * 
+ * + * @author Bernd Rinn + */ +public class SpringEoDSQLTransactionManager extends DataSourceTransactionManager +{ + @Override + protected void doBegin(Object transaction, TransactionDefinition definition) + { + super.doBegin(transaction, definition); + QueryTool.setManagedDatabaseConnection(((JdbcTransactionObjectSupport) transaction) + .getConnectionHolder().getConnection()); + } + + @Override + protected void doCleanupAfterCompletion(Object transaction) + { + super.doCleanupAfterCompletion(transaction); + QueryTool.clearManagedDatabaseConnection(); + } + +} + diff --git a/eodsql/src/net/lemnik/eodsql/QueryFactoryChain.java b/eodsql/src/net/lemnik/eodsql/QueryFactoryChain.java index 63e99a1..11ba5ae 100644 --- a/eodsql/src/net/lemnik/eodsql/QueryFactoryChain.java +++ b/eodsql/src/net/lemnik/eodsql/QueryFactoryChain.java @@ -11,6 +11,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import javax.sql.DataSource; import net.lemnik.eodsql.impl.DefaultQueryFactory; +import net.lemnik.eodsql.impl.ManagedConnections; import net.lemnik.eodsql.spi.QueryFactory; /** @@ -215,6 +216,67 @@ class QueryFactoryChain { } } + /** + * This implementation works exactly the same way as the + * {@link #create(DataSource, Class, ClassLoader)} method, + * but backs it's query instance with a managed connection from {@link ManagedConnections} + * instead of a {@code DataSource}. + * + * @param the generic type of query to create + * @param databaseName the name of the database to use the managed connections from + * @param query the class type instance of the query to implement + * @param loader the {@code ClassLoader} to use to create the query type + * @return an instance of the requested query, backed by the given + * {@code DataSource}, or {@literal null} if the query couldn't + * be created by any registered {@code QueryFactory} + * @throws InvalidQueryException + */ + T create( + final String databaseName, + final Class query, + final ClassLoader loader) + throws InvalidQueryException { + + if(factories.size() == 1) { + // This is a short-path for when only one + // QueryFactory is registered (will almost always be + // the case, since most of the time only + // DefaultQueryFactory is used) + final QueryFactory factory = factories.get(0); + + if(factory.canConstruct(query)) { + return factory.construct(databaseName, query, loader); + } else { + return null; + } + } else { + final CreateContext context = chain.get(); + context.incEntries(); + + T queryImpl = null; + + try { + final Iterator iterator = context.iterator; + + while(iterator.hasNext()) { + final QueryFactory factory = iterator.next(); + + if(factory.canConstruct(query)) { + queryImpl = factory.construct(databaseName, query, loader); + break; + } + } + } finally { + // cleanup after ourselves + if(context.decEntries() == 0) { + chain.remove(); + } + } + + return queryImpl; + } + } + void add(final QueryFactory factory) { // we add factories to the begining of the list // this means the DefaultQueryFactory will always appear last diff --git a/eodsql/src/net/lemnik/eodsql/QueryTool.java b/eodsql/src/net/lemnik/eodsql/QueryTool.java index c1ca3ed..5999ec4 100644 --- a/eodsql/src/net/lemnik/eodsql/QueryTool.java +++ b/eodsql/src/net/lemnik/eodsql/QueryTool.java @@ -19,6 +19,7 @@ import java.util.IdentityHashMap; import javax.sql.DataSource; import net.lemnik.eodsql.impl.ExceptionTranslationUtils; +import net.lemnik.eodsql.impl.ManagedConnections; import net.lemnik.eodsql.spi.QueryFactory; import net.lemnik.eodsql.spi.MethodImplementationFactory; @@ -310,6 +311,94 @@ public class QueryTool { } /** + * Creates a new instance of a BaseQuery class with the managed connection from a + * specific database. If the Connection is null this method + * will throw an IllegalArgumentException. + * @param the implementation type to return + * @see #getQuery(Class) + * @param database the connection to use for the query class + * @param query the BaseQuery to create an implementation of + * @param loader the ClassLoader that must load the new query class + * @return an implementation of the specified BaseQuery + * @throws InvalidDataTypeException if any data-type returned from a method in the + * query class cannot be mapped + * @throws InvalidQueryException if any method in the query class is not valid + */ + public static T getManagedQuery( + final String database, + final Class query, + final ClassLoader loader) + throws InvalidDataTypeException, + InvalidQueryException { + + return factoryChain.create(database, query, loader); + } + + /** + * Creates a new instance of a BaseQuery class with the managed connection from a + * specific database. If the Connection is null this method + * will throw an IllegalArgumentException. + * @param the implementation type to return + * @see #getQuery(Class) + * @param database the connection to use for the query class + * @param query the BaseQuery to create an implementation of + * @return an implementation of the specified BaseQuery + * @throws InvalidDataTypeException if any data-type returned from a method in the + * query class cannot be mapped + * @throws InvalidQueryException if any method in the query class is not valid + */ + public static T getManagedQuery( + final String database, + final Class query) + throws InvalidDataTypeException, + InvalidQueryException { + + return factoryChain.create(database, query, Thread.currentThread(). + getContextClassLoader()); + } + + /** + * Creates a new instance of a BaseQuery class with the managed connection from the + * default database. + * @param the implementation type to return + * @see #getQuery(Class) + * @param query the BaseQuery to create an implementation of + * @param loader the ClassLoader that must load the new query class + * @return an implementation of the specified BaseQuery + * @throws InvalidDataTypeException if any data-type returned from a method in the + * query class cannot be mapped + * @throws InvalidQueryException if any method in the query class is not valid + */ + public static T getManagedQuery( + final Class query, + final ClassLoader loader) + throws InvalidDataTypeException, + InvalidQueryException { + + return factoryChain.create(ManagedConnections.DEFAULT_DATABASE, query, loader); + } + + /** + * Creates a new instance of a BaseQuery class with the managed connection from the + * default database. + * @param the implementation type to return + * @see #getQuery(Class) + * @param query the BaseQuery to create an implementation of + * @return an implementation of the specified BaseQuery + * @throws InvalidDataTypeException if any data-type returned from a method in the + * query class cannot be mapped + * @throws InvalidQueryException if any method in the query class is not valid + */ + public static T getManagedQuery( + final Class query) + throws InvalidDataTypeException, + InvalidQueryException { + + return factoryChain.create(ManagedConnections.DEFAULT_DATABASE, query, Thread.currentThread(). + getContextClassLoader()); + } + + /** *

* Sometimes you need to perform a less structured "SELECT" than a Query interface allows. Although this method * is significantly slower than building a Query interface, it provides needed functionality that a Query @@ -364,6 +453,120 @@ public class QueryTool { *

* * @param the type to bind each row to + * @param type the data-type you want returned in the {@link DataSet} + * @param query the SQL query to be executed + * @return a connected {@code DataSet} containing the selected rows + * reflected as the row data-type requested + * @param parameters the parameters to be used in conjunction + * with the query string. + * @throws InvalidDataTypeException if the given {@code type} cannot + * be bound as a row data-type + * @throws InvalidQueryException if the query string given cannot + * be parsed, or doesn't match the given parameters + * @throws IllegalArgumentException is the DataSource + * provided is null + * + * @since 2.3 + */ + public static DataSet selectManaged( + final Class type, + final String query, + final Object... parameters) + throws InvalidDataTypeException, + InvalidQueryException { + + return selectManaged(ManagedConnections.DEFAULT_DATABASE, type, query, parameters); + } + + /** + *

+ * Sometimes you need to perform a less structured "SELECT" than a Query interface allows. Although this method + * is significantly slower than building a Query interface, it provides needed functionality that a Query + * interface cannot. The returned {@link DataSet} will be connected to the database and will be updateable + * and the Connection is obtained from the DataSource provided. + *

+ * This method does no caching of any sort, and the query will be re-parsed each time it is executed. It however + * provides the same primitive-type abstraction that a Query interface provides you with. The query string is + * parsed in the same way as a query string passed to an {@link Select @Select} annotation. + *

+ * + * @param the type to bind each row to + * @param databaseName the name of the database to get the managed connection for + * @param type the data-type you want returned in the {@link DataSet} + * @param query the SQL query to be executed + * @return a connected {@code DataSet} containing the selected rows + * reflected as the row data-type requested + * @param parameters the parameters to be used in conjunction + * with the query string. + * @throws InvalidDataTypeException if the given {@code type} cannot + * be bound as a row data-type + * @throws InvalidQueryException if the query string given cannot + * be parsed, or doesn't match the given parameters + * @throws IllegalArgumentException is the DataSource + * provided is null + * + * @since 2.3 + */ + public static DataSet selectManaged( + final String databaseName, + final Class type, + final String query, + final Object... parameters) + throws InvalidDataTypeException, + InvalidQueryException { + + if(databaseName == null) { + throw new IllegalArgumentException( + "The name of a database must be specified for QueryTool.selectManaged"); + } + + if(type == null) { + throw new IllegalArgumentException( + "The DataObject type must be non-null for QueryTool.selectManaged"); + } + + if(query == null || query.length() == 0) { + throw new IllegalArgumentException( + "You must specify a query for QueryTool.selectManaged"); + } + + DataObjectBinding.validate(type); + Connection conn = ManagedConnections.checkAndGetConnection(databaseName); + + try { + return QuickQueryUtil.selectDataSet( + conn, + true, + type, + query, + parameters); + } catch(final SQLException sqle) { + if (ExceptionTranslationUtils.isDefaultTranslator()) { + throw new InvalidQueryException( + "Couldn't execute query: '" + query + "'", sqle); + } else { + throw ExceptionTranslationUtils.translateException(conn, "QueryTool.selectManaged()", + query, sqle); + } + } catch(final ParseException pe) { + throw new InvalidQueryException( + "Cannot parser EoD SQL query: '" + query + "'", pe); + } + } + + /** + *

+ * Sometimes you need to perform a less structured "SELECT" than a Query interface allows. Although this method + * is significantly slower than building a Query interface, it provides needed functionality that a Query + * interface cannot. The returned {@link DataSet} will be connected to the database and will be updateable + * and the Connection is obtained from the DataSource provided. + *

+ * This method does no caching of any sort, and the query will be re-parsed each time it is executed. It however + * provides the same primitive-type abstraction that a Query interface provides you with. The query string is + * parsed in the same way as a query string passed to an {@link Select @Select} annotation. + *

+ * + * @param the type to bind each row to * @param dataSource the DataSource to obtain a database connection through * @param type the data-type you want returned in the {@link DataSet} * @param query the SQL query to be executed @@ -664,7 +867,107 @@ public class QueryTool { throw new InvalidQueryException( "Couldn't execute query: '" + query + "'", sqle); } else { - throw ExceptionTranslationUtils.translateException(connection, "QueryTool.select()", + throw ExceptionTranslationUtils.translateException(connection, "QueryTool.update()", + query, sqle); + } + } catch(ParseException pe) { + throw new InvalidQueryException( + "Cannot parser EoD SQL query: '" + query + "'", pe); + } + } + + /** + *

+ * Sometimes you need to perform a less structured "UPDATE", "INSERT" or "DELETE" than a Query + * interface allows. Although this method is significantly slower than building a Query interface, + * it provides needed functionality that a Query interface cannot. + *

+ * This method does no caching of any sort, and the query will be re-parsed each time it is executed. + * It however provides the same primitive-type abstraction that a Query interface provides you with. + * The query string is parsed in the same way as a query string passed to an {@link Select @Select} + * annotation. + *

+ * + * @param query the SQL query to be executed + * @param parameters the parameters to be used in + * conjunction with the query string. + * @return either (1) the row count for INSERT, UPDATE, + * or DELETE statements + * or (2) 0 for SQL statements that return nothing + * @throws InvalidDataTypeException if the given {@code type} cannot + * be bound as a row data-type + * @throws InvalidQueryException if the query string given cannot + * be parsed, or doesn't match the given parameters + * @throws IllegalArgumentException is the DataSource + * provided is null + * @since 2.3 + */ + public static int updateManaged( + final String query, + final Object... parameters) + throws InvalidDataTypeException, + InvalidQueryException { + + return updateManaged(ManagedConnections.DEFAULT_DATABASE, query, parameters); + } + + /** + *

+ * Sometimes you need to perform a less structured "UPDATE", "INSERT" or "DELETE" than a Query + * interface allows. Although this method is significantly slower than building a Query interface, + * it provides needed functionality that a Query interface cannot. + *

+ * This method does no caching of any sort, and the query will be re-parsed each time it is executed. + * It however provides the same primitive-type abstraction that a Query interface provides you with. + * The query string is parsed in the same way as a query string passed to an {@link Select @Select} + * annotation. + *

+ * + * @param databaseName the name of the database to get the managed connection for + * @param query the SQL query to be executed + * @param parameters the parameters to be used in + * conjunction with the query string. + * @return either (1) the row count for INSERT, UPDATE, + * or DELETE statements + * or (2) 0 for SQL statements that return nothing + * @throws InvalidDataTypeException if the given {@code type} cannot + * be bound as a row data-type + * @throws InvalidQueryException if the query string given cannot + * be parsed, or doesn't match the given parameters + * @throws IllegalArgumentException is the DataSource + * provided is null + * @since 2.3 + */ + public static int updateManaged( + final String databaseName, + final String query, + final Object... parameters) + throws InvalidDataTypeException, + InvalidQueryException { + + if(databaseName == null) { + throw new IllegalArgumentException( + "The name of a database must be specified for updateManaged"); + } + + if(query == null || query.length() == 0) { + throw new IllegalArgumentException( + "You must specify a query for updateManaged"); + } + Connection conn = ManagedConnections.checkAndGetConnection(databaseName); + + try { + return QuickQueryUtil.update( + conn, + false, + query, + parameters); + } catch(SQLException sqle) { + if (ExceptionTranslationUtils.isDefaultTranslator()) { + throw new InvalidQueryException( + "Couldn't execute query: '" + query + "'", sqle); + } else { + throw ExceptionTranslationUtils.translateException(conn, "QueryTool.updateManaged()", query, sqle); } } catch(ParseException pe) { @@ -889,5 +1192,41 @@ public class QueryTool { return factory; } + + /** + * Sets a managed database connection for database. + * + * @since 2.3 + */ + public static void setManagedDatabaseConnection(String database, Connection conn) { + ManagedConnections.setConnection(database, conn); + } + + /** + * Sets a managed database connection for the default database. + * + * @since 2.3 + */ + public static void setManagedDatabaseConnection(Connection conn) { + ManagedConnections.setConnection(ManagedConnections.DEFAULT_DATABASE, conn); + } + + /** + * Clears the managed database connection for database. + * + * @since 2.3 + */ + public static void clearManagedDatabaseConnection(String database) { + ManagedConnections.clearConnection(database); + } + + /** + * Clears the managed database connection for the default database. + * + * @since 2.3 + */ + public static void clearManagedDatabaseConnection() { + ManagedConnections.clearConnection(ManagedConnections.DEFAULT_DATABASE); + } } diff --git a/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java b/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java index 59bf7e8..f2ba902 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java +++ b/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java @@ -495,6 +495,35 @@ class BaseQueryImpl implements InvocationHandler { } } + static class ManagedConnectionSource implements ConnectionSource { + + private final String database; + + ManagedConnectionSource(final String database) { + if (database == null) { + throw new IllegalArgumentException("Database name cannot be null"); + } + this.database = database; + } + + public Connection getConnection() throws SQLException { + return ManagedConnections.checkAndGetConnection(database); + } + + public void releaseConnection( + final Connection connection) + throws SQLException { + } + + public void close() throws SQLException { + } + + public boolean isClosed() throws SQLException { + final Connection conn = ManagedConnections.getConnection(database); + return conn == null || conn.isClosed(); + } + } + static class DataSourceConnectionSource implements ConnectionSource { private static final ThreadLocal> diff --git a/eodsql/src/net/lemnik/eodsql/impl/DefaultQueryFactory.java b/eodsql/src/net/lemnik/eodsql/impl/DefaultQueryFactory.java index 600aa69..3e674df 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/DefaultQueryFactory.java +++ b/eodsql/src/net/lemnik/eodsql/impl/DefaultQueryFactory.java @@ -132,6 +132,20 @@ public class DefaultQueryFactory implements QueryFactory { } } + /** + * {@inheritDoc} + */ + public T construct(String databaseName, + Class query, ClassLoader loader) throws InvalidQueryException { + if(TransactionQuery.class.isAssignableFrom(query)) { + throw new InvalidQueryException("Cannot use TransactionQuery with managed connections."); + } + return construct( + new BaseQueryImpl.ManagedConnectionSource(databaseName), + query, + loader); + } + @SuppressWarnings("unchecked") private T constructProxy( final ClassLoader loader, diff --git a/eodsql/src/net/lemnik/eodsql/impl/ManagedConnections.java b/eodsql/src/net/lemnik/eodsql/impl/ManagedConnections.java new file mode 100644 index 0000000..355aba2 --- /dev/null +++ b/eodsql/src/net/lemnik/eodsql/impl/ManagedConnections.java @@ -0,0 +1,68 @@ +package net.lemnik.eodsql.impl; + +import java.sql.Connection; +import java.util.HashMap; +import java.util.Map; + +/** + * A store for managed connections. A "managed connection" is a connection being + * provided by a transaction manager. No transaction management or closing + * should be done on them. Database connections are per thread. + *

+ * This is an internal implementation class and should not be used by code + * outside EoDSQL. + * + * @author Bernd Rinn + */ +public class ManagedConnections { + + /** The default database. */ + public static final String DEFAULT_DATABASE = "DEFAULT"; + + private static final Map> connections = new HashMap>(); + + /** + * Sets the database connection for database. + */ + public synchronized static void setConnection(String database, + Connection newConnection) { + ThreadLocal conn = connections.get(database); + if (conn == null) { + conn = new ThreadLocal(); + connections.put(database, conn); + } + conn.set(newConnection); + } + + /** + * Clears the database connection for database. + */ + public static void clearConnection(String database) { + setConnection(database, null); + } + + /** + * Returns the database connection for database or null, if no database + * connection is available for this database. + */ + public synchronized static Connection getConnection(String database) { + final ThreadLocal conn = connections.get(database); + return conn != null ? conn.get() : null; + } + + /** + * Returns the database connection for database. + * + * @throw {@link IllegalStateException} if there is no database connection for database + */ + public synchronized static Connection checkAndGetConnection(String database) + throws IllegalStateException { + final ThreadLocal conn = connections.get(database); + final Connection connection = conn != null ? conn.get() : null; + if (connection == null) { + throw new IllegalStateException("No database connection for database " + database); + } + return connection; + } + +} diff --git a/eodsql/src/net/lemnik/eodsql/spi/QueryFactory.java b/eodsql/src/net/lemnik/eodsql/spi/QueryFactory.java index 6ce504a..ff2fb28 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/QueryFactory.java +++ b/eodsql/src/net/lemnik/eodsql/spi/QueryFactory.java @@ -6,6 +6,7 @@ import javax.sql.DataSource; import net.lemnik.eodsql.BaseQuery; import net.lemnik.eodsql.InvalidQueryException; +import net.lemnik.eodsql.impl.ManagedConnections; /** *

@@ -91,4 +92,21 @@ public interface QueryFactory { ClassLoader loader) throws InvalidQueryException; + /** + * Create an implementation of the specified query interface. The specified databaseName + * should be used by the implementation to get {@link ManagedConnections}. If a ClassLoader is + * required to create the implementation, the specified ClassLoader should be used. + * + * @param databaseName used by the implementation to fetch Connection objects from {@link ManagedConnections} + * @param query the query interface to implement + * @param loader the ClassLoader to use, if one is needed + * @return the query implementation + * @throws InvalidQueryException if query doesn't follow additional validation checks imposed by this + * QueryFactory + */ + T construct( + String databaseName, + Class query, + ClassLoader loader) + throws InvalidQueryException; } diff --git a/eodsql/test/net/lemnik/eodsql/EoDTestCase.java b/eodsql/test/net/lemnik/eodsql/EoDTestCase.java index d5f23ea..63c701e 100644 --- a/eodsql/test/net/lemnik/eodsql/EoDTestCase.java +++ b/eodsql/test/net/lemnik/eodsql/EoDTestCase.java @@ -36,6 +36,8 @@ public abstract class EoDTestCase extends TestCase { private static String url; + private static String url2; + private static String user; private static String password; @@ -44,11 +46,14 @@ public abstract class EoDTestCase extends TestCase { private Connection connection; + private Connection connection2; + private BasicDataSource dataSource; static { driverName = System.getProperty("test.db.driver"); url = System.getProperty("test.db.url"); + url2 = System.getProperty("test.db.url2"); user = System.getProperty("test.db.user"); password = System.getProperty("test.db.password"); @@ -56,6 +61,7 @@ public abstract class EoDTestCase extends TestCase { final Properties props = loadBuildProperties(); driverName = props.getProperty("test.db.driver"); url = props.getProperty("test.db.url"); + url2 = props.getProperty("test.db.url2"); user = props.getProperty("test.db.user"); password = props.getProperty("test.db.password"); } @@ -151,6 +157,17 @@ public abstract class EoDTestCase extends TestCase { delete(dir); } + protected Connection getConnection2() throws ClassNotFoundException, SQLException, IOException { + if(connection2 == null || connection2.isClosed()) { + Class.forName(driverName); + + connection2 = DriverManager.getConnection(url2, user, password); + loadConnection(connection2); + } + + return connection2; + } + protected Connection getConnection() throws ClassNotFoundException, SQLException, IOException { if(connection == null || connection.isClosed()) { Class.forName(driverName); diff --git a/eodsql/test/net/lemnik/eodsql/ManagedConnectionTest.java b/eodsql/test/net/lemnik/eodsql/ManagedConnectionTest.java new file mode 100644 index 0000000..e1646d9 --- /dev/null +++ b/eodsql/test/net/lemnik/eodsql/ManagedConnectionTest.java @@ -0,0 +1,113 @@ +package net.lemnik.eodsql; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import net.lemnik.eodsql.AccessibilityTestQuery.InaccessibleObject; + +/** + * Created on 2008/08/01 + * @author Jason Morris + */ +public class ManagedConnectionTest extends EoDTestCase { + AccessibilityTestQuery query = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + query = QueryTool.getManagedQuery(AccessibilityTestQuery.class); + QueryTool.setManagedDatabaseConnection(getConnection()); + query.create(); + } + + @Override + protected void tearDown() throws Exception { + query.drop(); + query.close(); + + super.tearDown(); + } + + public void testAccessibility() throws Exception { + List objects = new ArrayList(100); + + Random random = new Random(); + for(int i = 0; i < 100; i++) { + InaccessibleObject object = new InaccessibleObject(random.nextLong(), UUID.randomUUID().toString()); + query.insert(object); + objects.add(object); + } + + InaccessibleObject[] found = query.select(); + + assertEquals(objects.size(), found.length); + + for(int i = 0; i < found.length; i++) { + assertTrue("Couldn't find object: " + found[i] + " index " + i, objects.contains(found[i])); + } + } + + + public void testAccessibilityNonStandardDatabase() throws Exception { + final AccessibilityTestQuery query2 = QueryTool.getManagedQuery("mydb", AccessibilityTestQuery.class); + QueryTool.setManagedDatabaseConnection("mydb", getConnection2()); + query2.create(); + List objects = new ArrayList(100); + List objects2 = new ArrayList(100); + + Random random = new Random(); + for(int i = 0; i < 100; i++) { + InaccessibleObject object = new InaccessibleObject(random.nextLong(), UUID.randomUUID().toString()); + query.insert(object); + objects.add(object); + } + + for(int i = 0; i < 100; i++) { + InaccessibleObject object = new InaccessibleObject(random.nextLong(), UUID.randomUUID().toString()); + query2.insert(object); + objects2.add(object); + } + + InaccessibleObject[] found = query.select(); + + assertEquals(objects.size(), found.length); + + for(int i = 0; i < found.length; i++) { + assertTrue("Couldn't find object: " + found[i] + " index " + i, objects.contains(found[i])); + } + + InaccessibleObject[] found2 = query2.select(); + + assertEquals(objects.size(), found2.length); + + for(int i = 0; i < found2.length; i++) { + assertTrue("Couldn't find object: " + found2[i] + " index " + i, objects2.contains(found2[i])); + } + } + + + public void testNoConnection() throws Exception { + QueryTool.clearManagedDatabaseConnection(); + try + { + query.select(); + fail("Cleared database connection not detected."); + } catch (IllegalStateException ex) + { + // expected + } + QueryTool.setManagedDatabaseConnection(getConnection()); + } + + public void testNoTransactionQuery() throws Exception { + try { + QueryTool.getManagedQuery(TransactionTestQuery.class); + fail("TransactionQuery should be disallowed."); + } catch (InvalidQueryException ex) + { + // expected + } + } +} -- 1.8.0