Working now again.
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-test</artifactId>
+ <packaging>jar</packaging>
+ <name>wamblee.org support general library</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-general</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>javax.transaction</groupId>
+ <artifactId>transaction-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derby</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derbyclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derbynet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-dbcp</groupId>
+ <artifactId>commons-dbcp</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>toplink.essentials</groupId>
+ <artifactId>toplink-essentials</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>toplink.essentials</groupId>
+ <artifactId>toplink-essentials</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dbunit</groupId>
+ <artifactId>dbunit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-jdk14</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
--- /dev/null
+package org.wamblee.support.jndi;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.NamingException;
+
+public class StubInitialContext extends InitialContext {
+ private Map<String, Object> bindings = new HashMap<String, Object>();
+
+ public StubInitialContext() throws NamingException {
+ super(true);
+ }
+
+ @Override
+ public void bind(String name, Object obj) throws NamingException {
+ bindings.put(name, obj);
+ }
+
+ @Override
+ public Object lookup(String name) throws NamingException {
+ return bindings.get(name);
+ }
+
+ @Override
+ public Object lookup(Name name) throws NamingException {
+ return super.lookup(name.toString());
+ }
+}
--- /dev/null
+package org.wamblee.support.jndi;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.naming.spi.InitialContextFactory;
+
+/**
+ * Test initial context factory used for testing software in a Java SE
+ * environnment that uses JNDI to retrieve objects.
+ *
+ * See {@link #bind(String, Object)} to resp. register the initial context.
+ *
+ * To bind objects in the JNDI tree simply use the standard JNDI api: <code>
+ * InitialContext context = new InitialContext();
+ * MyClass myObj = ...;
+ * context.bind("a/b", myObj);
+ * </code>
+ */
+public class StubInitialContextFactory implements InitialContextFactory {
+
+ private static Context context;
+
+ private static void initialize() {
+ try {
+ context = new StubInitialContext();
+ } catch (NamingException e) { // can't happen.
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * This method must be called to register this initial context factory as
+ * the default implementation for JNDI.
+ *
+ * @throws Exception
+ */
+ public static void register() {
+ // sets up the InitialContextFactoryForTest as default factory.
+ System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
+ StubInitialContextFactory.class.getName());
+ if (context == null) {
+ initialize();
+ }
+ }
+
+ /**
+ * Unregisters the initial context factory
+ */
+ public static void unregister() {
+ System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "");
+ context = null;
+ }
+
+ public Context getInitialContext(Hashtable<?, ?> environment)
+ throws NamingException {
+ return context;
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.dbcp.ConnectionFactory;
+import org.apache.commons.dbcp.DriverManagerConnectionFactory;
+import org.apache.commons.dbcp.PoolableConnectionFactory;
+import org.apache.commons.dbcp.PoolingDataSource;
+import org.apache.commons.pool.impl.GenericObjectPool;
+
+public abstract class AbstractDatabase implements Database {
+ private static final int CONNECTION_POOL_SIZE = 16;
+
+ private DataSource itsDataSource;
+
+ private boolean started;
+
+ protected AbstractDatabase() {
+ started = false;
+ }
+
+ protected abstract void doStart();
+
+ protected abstract void doStop();
+
+ /**
+ * This method must be called from the start method.
+ */
+ protected final void createDataSource() {
+ GenericObjectPool connectionPool = new GenericObjectPool(null);
+ connectionPool.setMaxActive(CONNECTION_POOL_SIZE);
+ ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(getJdbcUrl(), getUsername(), getPassword());
+ // The following line must be kept in although it does not appear to be used, the constructor regsiters the
+ // constructed object at the connection pool.
+ PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory,connectionPool,null,null,false,true);
+ itsDataSource = new PoolingDataSource(connectionPool);
+ }
+
+
+ /// BELOW THIS LINE IS NOT OF INTEREST TO SUBCLASSES.
+
+ public final DataSource start() {
+ if ( started ) {
+ throw new RuntimeException("Database already started");
+ }
+ started = true;
+ doStart();
+ return getDatasource();
+ }
+
+ public final void stop() {
+ if ( ! started ) {
+ return; // nothing to do.
+ }
+ started = false;
+ doStop();
+ }
+
+ private final DataSource getDatasource() {
+ if ( !started) {
+ throw new RuntimeException("Database is not started!");
+ }
+ return itsDataSource;
+ }
+
+ protected String getProperty(String aName) {
+ String value = System.getProperty(aName);
+ if ( value != null ) {
+ return value;
+ }
+ value = System.getenv(aName);
+ if ( value != null ) {
+ return value;
+ }
+ throw new RuntimeException("This class expects the '" + aName + "' property to be set");
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.List;
+
+public abstract class AbstractDatabaseProvider implements DatabaseProvider {
+
+ protected abstract List<String> getCapabilities();
+
+ public final boolean supportsCapabilities(String[] aCapabilities) {
+ for (String capability: aCapabilities) {
+ if ( !getCapabilities().contains(capability)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wamblee.support.persistence;
+
+import java.util.List;
+
+import javax.sql.DataSource;
+
+/**
+ * Represents a database.
+ *
+ * @author Erik Brakkee
+ */
+public interface Database {
+
+ /**
+ * Starts a database. This call should not block and return as soon as the
+ * database has been started.
+ */
+ DataSource start();
+
+ /**
+ * Gets the Jdbc Url to connect to this database.
+ *
+ * @return Jdbc Url.
+ */
+ String getJdbcUrl();
+
+ /**
+ * Gets the external Jdbc URL to connect to this database from other JVMs.
+ */
+ String getExternalJdbcUrl();
+
+ /**
+ * Gets the username to connect to the database.
+ *
+ * @return username.
+ */
+ String getUsername();
+
+ /**
+ * Gets the password to connect to the database.
+ *
+ * @return password.
+ */
+ String getPassword();
+
+ /**
+ * Stops a database.
+ */
+ void stop();
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.logging.Logger;
+
+/**
+ * DatabaseBuilder is used from unit test to obtain a reference to a database
+ * from unit test. This database is either an inmemory database or represents an
+ * external database. Purpose of this utility is to make test code independent of the
+ * particular database used and specifically to be able to run database tests without
+ * any configuration at all (using an inmemory database).
+ *
+ * The type of database to use can be overridden by specifying either a system
+ * property or an environment variable ({@link #DB_CAPABILITIES_PROP}) that
+ * contains the comma-separated capabilities of the database. Each database type
+ * provides its own capabilities (see {@link DatabaseProvider} implementations}.
+ *
+ *
+ * There are currently two database types available:
+ * <ul>
+ * <li> Derby: AN inmemory derby. Provided by {@link DerbyDatabaseProvider}. This is the default.
+ * </li>
+ * <li> External: An arbitrary external database configured using system properties or environment variables
+ * in the usual way using a JDBC URL, username, and password.
+ * </li>
+ * </ul>
+ *
+ * The <code>DatabaseBuilder</code> uses the {@link ServiceLoader} mechanism to find implementations
+ * of {@link DatabaseProvider} on the classpath. In the {@link #getDatabase(String...)} method a number of
+ * capabilities are passed. The database providers are then searched in (arbitrary) order and the first one that
+ * has all required capabilities is returned.
+ *
+ * {@link #getSupportedDatabases()} gives a list of all available databases.
+ */
+public class DatabaseBuilder {
+
+ private static final Logger LOGGER = Logger.getLogger(DatabaseBuilder.class
+ .getName());
+
+ /**
+ * Environmment variable by which capabilities of the requested database can
+ * be defined
+ */
+ private static final String DB_CAPABILITIES_PROP = "TEST_DB_CAPABILITIES";
+
+ private static ServiceLoader<DatabaseProvider> LOADER = null;
+
+ private DatabaseBuilder() {
+ // Empty.
+ }
+
+ private static String[] parseCapabilities(String aValue) {
+ return aValue.split(",");
+ }
+
+ /**
+ * Gets the first database that has all required capabilities.
+ * @param aCapabilities Capabilities.
+ * @return Database to use.
+ */
+ public static Database getDatabase(String... aCapabilities) {
+ if (aCapabilities.length == 0) {
+ LOGGER.info("Examining database capabilities");
+ LOGGER.info(" Checking system property " + DB_CAPABILITIES_PROP);
+ String capabilities = System.getProperty(DB_CAPABILITIES_PROP);
+ if (capabilities != null) {
+ aCapabilities = parseCapabilities(capabilities);
+ } else {
+ LOGGER.info(" Checking environment variable "
+ + DB_CAPABILITIES_PROP);
+ capabilities = System.getenv(DB_CAPABILITIES_PROP);
+ if (capabilities != null) {
+ aCapabilities = parseCapabilities(capabilities);
+ } else {
+ LOGGER.info(" Using default capabilities");
+ aCapabilities = new String[] { DatabaseProvider.CAPABILITY_IN_MEMORY };
+ }
+ }
+ LOGGER.info("Using capabilities: " + aCapabilities);
+ }
+ synchronized (DatabaseBuilder.class) {
+ initLoader();
+ for (DatabaseProvider db : LOADER) {
+ if (db.supportsCapabilities(aCapabilities)) {
+ return db.create();
+ }
+ }
+ }
+ throw new RuntimeException(
+ "No database found that satisfies capabilities: "
+ + Arrays.asList(aCapabilities));
+ }
+
+ /**
+ * Gets a list of available databases.
+ * @return List of databases.
+ */
+ public static List<DatabaseDescription> getSupportedDatabases() {
+ initLoader();
+ List<DatabaseDescription> descriptions = new ArrayList<DatabaseDescription>();
+ for (DatabaseProvider db : LOADER) {
+ descriptions.add(db.getDescription());
+ }
+ return descriptions;
+ }
+
+ private static void initLoader() {
+ if (LOADER == null) {
+ LOADER = ServiceLoader.load(DatabaseProvider.class);
+ }
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+/**
+ * Description of a specific database.
+ */
+public class DatabaseDescription {
+
+ private String[] itsCapabilities;
+ private String itsDatabase;
+ private String itsOther;
+
+ /**
+ * Constructs the description.
+ * @param aCapabilities List of all capabilities.
+ * @param aDatabase Database.
+ * @param aOther Other information.
+ */
+ public DatabaseDescription(String[] aCapabilities, String aDatabase, String aOther) {
+ itsCapabilities = aCapabilities;
+ itsDatabase = aDatabase;
+ itsOther = aOther;
+ }
+
+ @Override
+ public String toString() {
+ return "\n Database " + itsDatabase +
+ "\n Capabilities: " + printCapabilities() +
+ "\n Other info: " + itsOther;
+ }
+
+ private String printCapabilities() {
+ String res = "";
+ for (int i = 0; i < itsCapabilities.length; i++) {
+ res += "" + itsCapabilities[i];
+ if ( i < itsCapabilities.length -1 ) {
+ res += ", ";
+ }
+ }
+ return res;
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+/**
+ * Database provider. This database provider represents a particular type of
+ * database such as its capabilities and the ability to create an instance
+ * representing a running database.
+ *
+ * Since the {@link DatabaseBuilder} uses a first match algorithm and the order
+ * of databaseproviders is not guaranteed, it is recommended for each database
+ * provider to also provide a unique capability that no other database has.
+ */
+public interface DatabaseProvider {
+
+ /**
+ * Capability that all databases that run inmemory have.
+ */
+ String CAPABILITY_IN_MEMORY = "INMEMORY";
+
+ /**
+ * Capability that all databases that are external have.
+ */
+ String CAPABILITY_EXTERNAL = "EXTERNAL";
+
+ /**
+ * Determines if the database has all capabilities that are requested.
+ * @param aCapabilities Capabilities it must ahve
+ * @return True if it has all capabilities.
+ */
+ boolean supportsCapabilities(String[] aCapabilities);
+
+ /**
+ * Gets the description for the database.
+ * @return Description.
+ */
+ DatabaseDescription getDescription();
+
+ /**
+ * Creates a database instance that represents a running instance of that database.
+ * @return Database.
+ */
+ Database create();
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import junit.framework.TestCase;
+
+import org.dbunit.DataSourceDatabaseTester;
+import org.dbunit.DatabaseTestCase;
+import org.dbunit.DatabaseUnitException;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.database.DatabaseConnection;
+import org.dbunit.database.DatabaseSequenceFilter;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.FilteredDataSet;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.filter.ITableFilter;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+import org.dbunit.operation.DatabaseOperation;
+
+/**
+ * Database utilities is a simple support class for common tasks in working with
+ * databases.
+ */
+public class DatabaseUtils {
+
+ public static interface TableSet {
+ boolean contains(String aTableName);
+ }
+
+ public static interface JdbcUnitOfWork<T> {
+ T execute(Connection aConnection) throws Exception;
+ }
+
+ public static interface TableSetOperation {
+ void execute(String aTable) throws Exception;
+ }
+
+ private static final Logger LOG = Logger.getLogger(DatabaseUtils.class
+ .getName());
+
+ /**
+ * Schema pattern.
+ */
+ private static final String SCHEMA_PATTERN = "%";
+ private DataSource dataSource;
+ private ITableFilterSimple tables;
+
+
+ public DatabaseUtils(DataSource aDataSource, ITableFilterSimple aTables) {
+ dataSource = aDataSource;
+ tables = aTables;
+ }
+
+ public IDatabaseTester createDbTester() throws Exception {
+ return createDbTester(getTableNames(tables));
+ }
+
+ public IDatabaseTester createDbTester(String[] aTables) throws Exception {
+ IDatabaseTester dbtester = new DataSourceDatabaseTester(dataSource);
+ dbtester.setDataSet(dbtester.getConnection().createDataSet(aTables));
+ return dbtester;
+ }
+
+ public void cleanDatabase() throws Exception {
+ cleanDatabase(tables);
+ }
+
+ public void executeOnTables(ITableFilterSimple aTables,
+ final TableSetOperation aOperation) throws Exception {
+ final String[] tables = getTableNames(aTables);
+ executeInTransaction(new JdbcUnitOfWork<Void>() {
+ public Void execute(Connection aConnection) throws Exception {
+ for (int i = tables.length - 1; i >= 0; i--) {
+ aOperation.execute(tables[i]);
+ }
+ return null;
+ }
+ });
+ for (String table : tables) {
+
+ }
+ }
+
+ public void cleanDatabase(ITableFilterSimple aSelection) throws Exception {
+
+ final String[] tables = getTableNames(aSelection);
+ executeInTransaction(new JdbcUnitOfWork<Void>() {
+
+ public Void execute(Connection aConnection) throws Exception {
+ IDatabaseConnection connection = new DatabaseConnection(
+ aConnection);
+ ITableFilter filter = new DatabaseSequenceFilter(connection,
+ tables);
+ IDataSet dataset = new FilteredDataSet(filter, connection
+ .createDataSet(tables));
+ DatabaseOperation.DELETE_ALL.execute(connection, dataset);
+ return null;
+ }
+ });
+
+ }
+
+ public <T> T executeInTransaction(JdbcUnitOfWork<T> aCallback)
+ throws Exception {
+ Connection connection = dataSource.getConnection();
+ try {
+ T value = aCallback.execute(connection);
+ connection.commit();
+ return value;
+ } finally {
+ connection.close();
+ }
+ }
+
+ public String[] getTableNames() throws Exception {
+ return getTableNames(tables);
+ }
+
+ /**
+ * @throws SQLException
+ */
+ public String[] getTableNames(ITableFilterSimple aSelection)
+ throws Exception {
+
+ List<String> result = new ArrayList<String>();
+ LOG.fine("Getting database table names to clean (schema: '"
+ + SCHEMA_PATTERN + "'");
+
+ ResultSet tables = dataSource.getConnection().getMetaData().getTables(
+ null, SCHEMA_PATTERN, "%", new String[] { "TABLE" });
+ while (tables.next()) {
+ String table = tables.getString("TABLE_NAME");
+ if (aSelection.accept(table)) {
+ result.add(table);
+ }
+ }
+ return (String[]) result.toArray(new String[0]);
+ }
+
+ public void emptyTables() throws Exception {
+ executeOnTables(tables, new TableSetOperation() {
+ public void execute(String aTable) throws Exception {
+ emptyTable(aTable);
+ }
+ });
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTables(final ITableFilterSimple aSelection)
+ throws Exception {
+ executeOnTables(aSelection, new TableSetOperation() {
+ public void execute(String aTable) throws Exception {
+ emptyTable(aTable);
+ }
+ });
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTable(String aTable) throws Exception {
+ executeSql("delete from " + aTable);
+ }
+
+ public void dropTables() throws Exception {
+ executeOnTables(tables, new TableSetOperation() {
+
+ public void execute(String aTable) throws Exception {
+ dropTable(aTable);
+ }
+ });
+ }
+
+
+ public void dropTables(ITableFilterSimple aTables) throws Exception {
+ executeOnTables(aTables, new TableSetOperation() {
+
+ public void execute(String aTable) throws Exception {
+ dropTable(aTable);
+ }
+ });
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void dropTable(final String aTable) throws Exception {
+ executeInTransaction(new JdbcUnitOfWork<Void>() {
+ public Void execute(Connection aConnection) throws Exception {
+ executeUpdate(aConnection, "drop table " + aTable);
+ return null;
+ }
+ });
+
+ }
+
+ /**
+ * Executes an SQL statement within a transaction.
+ *
+ * @param aSql
+ * SQL statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql) throws Exception {
+ return executeSql(aSql, new Object[0]);
+ }
+
+ /**
+ * Executes an SQL statement within a transaction. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL statement.
+ * @param aArg
+ * Argument of the sql statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql, final Object aArg)
+ throws Exception {
+ return executeSql(aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes an sql statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL query to execute.
+ * @param aArgs
+ * Arguments.
+ * @return Number of rows updated.
+ */
+ public int executeSql(final String aSql, final Object[] aArgs)
+ throws Exception {
+ return executeInTransaction(new JdbcUnitOfWork<Integer>() {
+ public Integer execute(Connection aConnection) throws Exception {
+ PreparedStatement stmt = aConnection.prepareStatement(aSql);
+ setPreparedParams(aArgs, stmt);
+ return stmt.executeUpdate();
+ }
+ });
+ }
+
+ /**
+ * Executes an SQL query.
+ *
+ * @param aSql
+ * Query to execute.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(Connection aConnection, String aSql) {
+ return executeQuery(aConnection, aSql, new Object[0]);
+ }
+
+ /**
+ * Executes a query with a single argument. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Query.
+ * @param aArg
+ * Argument.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(Connection aConnection, String aSql,
+ Object aArg) {
+ return executeQuery(aConnection, aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes a query. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Sql query.
+ * @param aArgs
+ * Arguments to the query.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(Connection aConnection, final String aSql,
+ final Object[] aArgs) {
+ try {
+ PreparedStatement statement = aConnection.prepareStatement(aSql);
+ setPreparedParams(aArgs, statement);
+
+ return statement.executeQuery();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public int executeUpdate(Connection aConnection, final String aSql,
+ final Object... aArgs) {
+ try {
+ PreparedStatement statement = aConnection.prepareStatement(aSql);
+ setPreparedParams(aArgs, statement);
+
+ return statement.executeUpdate();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets the values of a prepared statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aArgs
+ * Arguments to the prepared statement.
+ * @param aStatement
+ * Prepared statement
+ * @throws SQLException
+ */
+ private void setPreparedParams(final Object[] aArgs,
+ PreparedStatement aStatement) throws SQLException {
+ for (int i = 1; i <= aArgs.length; i++) {
+ setPreparedParam(i, aStatement, aArgs[i - 1]);
+ }
+ }
+
+ /**
+ * Sets a prepared statement parameter.
+ *
+ * @param aIndex
+ * Index of the parameter.
+ * @param aStatement
+ * Prepared statement.
+ * @param aObject
+ * Value Must be of type Integer, Long, or String.
+ * @throws SQLException
+ */
+ private void setPreparedParam(int aIndex, PreparedStatement aStatement,
+ Object aObject) throws SQLException {
+ if (aObject instanceof Integer) {
+ aStatement.setInt(aIndex, ((Integer) aObject).intValue());
+ } else if (aObject instanceof Long) {
+ aStatement.setLong(aIndex, ((Integer) aObject).longValue());
+ } else if (aObject instanceof String) {
+ aStatement.setString(aIndex, (String) aObject);
+ } else {
+ TestCase.fail("Unsupported object type for prepared statement: "
+ + aObject.getClass() + " value: " + aObject
+ + " statement: " + aStatement);
+ }
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public int getTableSize(final String aTable) throws Exception {
+ return executeInTransaction(new JdbcUnitOfWork<Integer>() {
+ public Integer execute(Connection aConnection) throws Exception {
+ ResultSet resultSet = executeQuery(aConnection,
+ "select count(*) from " + aTable);
+ resultSet.next();
+ return resultSet.getInt(1);
+ }
+ });
+
+ }
+
+ public int countResultSet(ResultSet aResultSet) throws SQLException {
+ int count = 0;
+
+ while (aResultSet.next()) {
+ count++;
+ }
+
+ return count;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wamblee.support.persistence;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import junit.framework.TestCase;
+
+import org.apache.derby.drda.NetworkServerControl;
+import org.wamblee.io.FileSystemUtils;
+
+/**
+ * Derby database setup. The external JDBC url used to connect to a running
+ * instance is
+ *
+ * <pre>
+ * jdbc:derby:net://localhost:1527/testdb
+ * </pre>
+ *
+ * and the driver class is
+ *
+ * <pre>
+ * com.ibm.db2.jcc.DB2Driver
+ * </pre>
+ *
+ * The following jars will have to be used <code>db2jcc.jar</code> and
+ * <code>db2jcc_license_c.jar</code>.
+ */
+public class DerbyDatabase extends AbstractDatabase {
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOGGER = Logger.getLogger(DerbyDatabase.class
+ .getName());
+
+ /**
+ * Database user name.
+ */
+ private static final String USERNAME = "sa";
+
+ /**
+ * Database password.
+ */
+ private static final String PASSWORD = "123";
+ /**
+ * Poll interval for the checking the server status.
+ */
+ private static final int POLL_INTERVAL = 100;
+
+ /**
+ * Maximum time to wait until the server has started or stopped.
+ */
+ private static final int MAX_WAIT_TIME = 10000;
+
+ /**
+ * Database name to use.
+ */
+ private static final String DATABASE_NAME = "testdb";
+
+ /**
+ * Path on the file system where derby files are stored.
+ */
+ private static final String DATABASE_PATH = "target/db/persistence/derby";
+
+ /**
+ * Derby property required to set the file system path
+ * {@link #DATABASE_PATH}.
+ */
+ private static final String SYSTEM_PATH_PROPERTY = "derby.system.home";
+
+
+ private boolean inmemory;
+
+
+ /**
+ * Constructs derby database class to allow creation of derby database
+ * instances.
+ */
+ public DerbyDatabase() {
+ inmemory = true;
+ }
+
+ public DerbyDatabase(boolean aInMemoryFlag) {
+ inmemory = aInMemoryFlag;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.persistence.Database#start()
+ */
+ public void doStart() {
+ try {
+ // just in case a previous run was killed without the
+ // cleanup
+ cleanPersistentStorage();
+
+ if (!inmemory) {
+ // set database path.
+ Properties lProperties = System.getProperties();
+ lProperties.put(SYSTEM_PATH_PROPERTY, DATABASE_PATH);
+ }
+
+ Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
+
+ runDatabase();
+
+ waitUntilStartedOrStopped(true);
+
+ // Force creation of the database.
+ Connection lConnection = createConnection();
+ lConnection.close();
+
+ LOGGER.info("Database started: \n URL = " + getExternalJdbcUrl() + "\n user = " + getUsername() + "\n password = "
+ + getPassword());
+
+ createDataSource();
+
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ if (isStarted()) {
+ LOGGER.warning("Shutting down db");
+ DerbyDatabase.this.stop();
+ }
+ }
+ });
+ } catch (Exception e) {
+ throw new RuntimeException("Problem starting database", e);
+ }
+ }
+
+ /**
+ * Waits until the database server has started or stopped.
+ *
+ * @param aStarted
+ * If true, waits until the server is up, if false, waits until
+ * the server is down.
+ * @throws InterruptedException
+ */
+ private void waitUntilStartedOrStopped(boolean aStarted)
+ throws InterruptedException {
+ long lWaited = 0;
+
+ while (aStarted != isStarted()) {
+ Thread.sleep(POLL_INTERVAL);
+ lWaited += POLL_INTERVAL;
+
+ if (lWaited > MAX_WAIT_TIME) {
+ throw new RuntimeException(
+ "Derby database did not start within " + MAX_WAIT_TIME
+ + "ms");
+ }
+ }
+ }
+
+ /**
+ * Checks if the database server has started or not.
+ *
+ * @return True if started, false otherwise.
+ */
+ private boolean isStarted() {
+ try {
+ getControl().ping();
+
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Gets the controller for the database server.
+ *
+ * @return Controller.
+ * @throws Exception
+ */
+ private NetworkServerControl getControl() throws Exception {
+ return new NetworkServerControl();
+ }
+
+ /**
+ * Runs the database.
+ *
+ */
+ private void runDatabase() {
+ try {
+ getControl().start(new PrintWriter(System.out));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.persistence.Database#getJdbcUrl()
+ */
+ public String getJdbcUrl() {
+ return getBaseJdbcUrl()
+ + ";create=true;retrieveMessagesFromServerOnGetMessage=true;";
+ }
+
+ private String getBaseJdbcUrl() {
+ return (inmemory ? "jdbc:derby:memory:": "jdbc:derby:") + DATABASE_NAME;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.persistence.Database#getExternalJdbcUrl()
+ */
+ public String getExternalJdbcUrl() {
+ return "jdbc:derby://localhost:1527/" + (inmemory ? "memory:" : "") + DATABASE_NAME;
+ }
+
+ /**
+ * Shuts down the derby database and cleans up all created files.
+ *
+ */
+ private void shutdownDerby() {
+ try {
+ DriverManager.getConnection("jdbc:derby:;shutdown=true");
+ throw new RuntimeException("Derby did not shutdown, "
+ + " should always throw exception at shutdown");
+ } catch (Exception e) {
+ LOGGER.info("Derby has been shut down.");
+ }
+ }
+
+ /**
+ * Gets the user name.
+ */
+ public String getUsername() {
+ return USERNAME;
+ }
+
+ /**
+ * Gets the password.
+ *
+ * @return
+ */
+ public String getPassword() {
+ return PASSWORD;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.persistence.Database#createConnection()
+ */
+ public Connection createConnection() throws SQLException {
+ Connection c = DriverManager.getConnection(getJdbcUrl(), getUsername(),
+ getPassword());
+
+ return c;
+ }
+
+
+ /**
+ * Stops the derby database and cleans up all derby files.
+ */
+ public void doStop() {
+ try {
+ // shutdown network server.
+ getControl().shutdown();
+ waitUntilStartedOrStopped(false);
+
+ // shutdown inmemory access.
+ shutdownDerby();
+ cleanPersistentStorage();
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "Problem stopping database", e);
+ }
+ }
+
+ /**
+ * Cleans up persistent storage of Derby.
+ */
+ private void cleanPersistentStorage() {
+ File lFile = new File(DATABASE_PATH);
+
+ if (lFile.isFile()) {
+ TestCase.fail("A regular file by the name " + DATABASE_PATH
+ + " exists, clean this up first");
+ }
+
+ if (!lFile.isDirectory()) {
+ return; // no-op already cleanup up.
+ }
+ FileSystemUtils.deleteDirRecursively(DATABASE_PATH);
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class DerbyDatabaseProvider extends AbstractDatabaseProvider {
+
+ /**
+ * Capabilities of this type of database.
+ */
+ public static final List<String> CAPABILIITIES =
+ Arrays.asList(DatabaseProvider.CAPABILITY_IN_MEMORY, "DERBY");
+
+
+ public DerbyDatabaseProvider() {
+ // Empty
+ }
+
+ public Database create() {
+ return new DerbyDatabase();
+ }
+
+ public DatabaseDescription getDescription() {
+ return new DatabaseDescription(CAPABILIITIES.toArray(new String[0]),
+ "Derby", "In-memory, volatile, set breakpoint to debug");
+ }
+
+ @Override
+ protected List<String> getCapabilities() {
+ return CAPABILIITIES;
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.dbcp.ConnectionFactory;
+import org.apache.commons.dbcp.DriverManagerConnectionFactory;
+import org.apache.commons.dbcp.PoolableConnectionFactory;
+import org.apache.commons.dbcp.PoolingDataSource;
+import org.apache.commons.pool.impl.GenericObjectPool;
+
+/**
+ * Database that encapsulates connection to an external database.
+ * Database connection details can be configured through system properties
+ * and environment variables, see {@link #DB_URL_PROP}, {@link #DB_USER_PROP}, and
+ * {@link #DB_PASSWORD_PROP|.
+ *
+ * This class assumes modern database drivers that work together with java.util.ServiceLoader
+ * so that explicitly doing a Class.forName() is not necessary to load the database driver.
+ */
+public class ExternalDatabase extends AbstractDatabase {
+
+ private static final Logger LOGGER = Logger.getLogger(ExternalDatabase.class.getName());
+
+ /**
+ * System property/environment variable that defines the database URL.
+ */
+ public static final String DB_URL_PROP = "TEST_DB_URL";
+
+ /**
+ * System property/environment variable that defines the database user.
+ */
+ public static final String DB_USER_PROP = "TEST_DB_USER";
+
+ /**
+ * System property/environment variable that defines the database password.
+ */
+ public static final String DB_PASSWORD_PROP = "TEST_DB_PASSWORD";
+
+
+ private String itsUrl;
+ private String itsUser;
+ private String itsPassword;
+
+ private DataSource itsDataSource;
+
+ public ExternalDatabase() {
+ // Empty
+ }
+
+ public String getExternalJdbcUrl() {
+ return itsUrl;
+ }
+
+ public String getJdbcUrl() {
+ return itsUrl;
+ }
+
+ public String getPassword() {
+ return itsPassword;
+ }
+
+ public String getUsername() {
+ return itsUser;
+ }
+
+ public void doStart() {
+ itsUrl = getProperty(DB_URL_PROP);
+ itsUser = getProperty(DB_USER_PROP);
+ itsPassword = getProperty(DB_PASSWORD_PROP);
+
+ createDataSource();
+ }
+
+ public void doStop() {
+ // Empty.
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class ExternalDatabaseProvider extends AbstractDatabaseProvider {
+
+ /**
+ * Capabilities of this type of database.
+ */
+ public static final List<String> CAPABILIITIES = Arrays
+ .asList(CAPABILITY_EXTERNAL);
+
+ @Override
+ protected List<String> getCapabilities() {
+ return CAPABILIITIES;
+ }
+
+ public Database create() {
+ return new ExternalDatabase();
+ }
+
+ public DatabaseDescription getDescription() {
+ return new DatabaseDescription(
+ CAPABILIITIES.toArray(new String[0]),
+ "External Database",
+ "Any database as described by the JDBC URL: requires system properties or environment variables: "
+ + ExternalDatabase.DB_URL_PROP
+ + ", "
+ + ExternalDatabase.DB_USER_PROP
+ + ", and "
+ + ExternalDatabase.DB_PASSWORD_PROP);
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.sql.SQLException;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.persistence.Persistence;
+import javax.sql.DataSource;
+
+import org.wamblee.support.jndi.StubInitialContextFactory;
+import org.wamblee.support.persistence.toplink.JndiSessionCustomizer;
+
+
+/**
+ * Utility for building an appropriately configured EntityManagerFactory. The
+ * idea is that a persistence.xml is used unchanged from the production version.
+ * This utility will then add the additional properties required for execution
+ * in a standalone environment.
+ *
+ * The other purpose is to to shield dependencies of the test code on a
+ * particular JPA provider.
+ */
+public class JpaBuilder {
+
+ private static final Logger LOGGER = Logger.getLogger(JpaBuilder.class
+ .getName());
+
+ /**
+ * Callback interface to execute some JPA code within a transaction with the
+ * entitymanager to use provided as input.
+ */
+ public static interface JpaUnitOfWork<T> {
+ /**
+ * Executes the unit of work. A transaction has been started.
+ * @param em Entity manager.
+ * @return Result of the execute method. If you don't want to return anything use
+ * <code>Void</code> for the return type and return null from the implementation.
+ */
+ T execute(EntityManager em);
+ }
+
+ private PersistenceUnitDescription persistenceUnit;
+ private DataSource dataSource;
+ private EntityManagerFactory factory;
+
+ /**
+ * Constructs the builder.
+ *
+ * @param aDataSource
+ * Datasource of database.
+ * @param aPersistenceUnit
+ * Persistence unit.
+ */
+ public JpaBuilder(DataSource aDataSource,
+ PersistenceUnitDescription aPersistenceUnit) {
+ persistenceUnit = aPersistenceUnit;
+ dataSource = aDataSource;
+ StubInitialContextFactory.register();
+ }
+
+ /**
+ * Starts the builder, which in particular, mocks JNDI, binds the datasource
+ * the JNDI where the persistence unit expects it, creates the entity
+ * manager factory, and forces creation of the database schema.
+ */
+ public void start() throws Exception {
+ try {
+ InitialContext ctx = new InitialContext();
+ ctx.bind(persistenceUnit.getJndiName(), dataSource);
+ } catch (NamingException e) {
+ throw new RuntimeException("JNDI problem", e);
+ }
+ factory = createFactory();
+ execute(new JpaUnitOfWork<Void>() {
+ public Void execute(EntityManager em) {
+ // Empty, just to trigger database schema creation.
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Stops the entity manager factory and disables JNDI mocking.
+ */
+ public void stop() {
+ StubInitialContextFactory.unregister();
+ factory.close();
+ }
+
+ /**
+ * Creates a new entity manager factory. Typically not used by test code.
+ * @return Entity manager factory.
+ */
+ public EntityManagerFactory createFactory() {
+ Map<String, String> jpaProps = new TreeMap<String, String>();
+ jpaProps.put("toplink.session.customizer", JndiSessionCustomizer.class
+ .getName());
+ jpaProps.put("toplink.ddl-generation", "create-tables");
+ return Persistence.createEntityManagerFactory(persistenceUnit
+ .getUnitName(), jpaProps);
+ }
+
+ /**
+ * Executes a unit of work. This creates an entitymanager and runs the
+ * {@link JpaUnitOfWork#execute(EntityManager)} within a transaction, passing
+ * it the entity manager. Use of this method saves a lot of typing for applications.
+ *
+ * @param aWork Work to execute.
+ * @return The return value of the execute method of the unit of work.
+ */
+ public <T> T execute(JpaUnitOfWork<T> aWork) throws Exception {
+ EntityManager em = factory.createEntityManager();
+ EntityTransaction transaction = em.getTransaction();
+ transaction.begin();
+ try {
+ T value = aWork.execute(em);
+ transaction.commit();
+ return value;
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "Exception occured", e);
+ transaction.rollback();
+ throw e;
+ } finally {
+ em.close();
+ }
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import javax.sql.DataSource;
+
+import org.dbunit.IDatabaseTester;
+
+/**
+ * This class is the entry point for JPA tests. Test code should construct a JpaTester in the
+ * <code>@Before</code> method and call {@link #start()} on it in that method. Also, test code should
+ * call {@link #stop()} on it in the <code>@After</code> method.
+ *
+ * This class is constructed with a description of the persistence unit to be tested. The principle is that
+ * an existing <code>persistence.xml</code> can be tested without change in unit test code.
+ *
+ * It then takes care of the following:
+ * <ul>
+ * <li> Creating an inmemory database for testing (default) or connecting to an external database.
+ * See {@link DatabaseBuilder} for more information on how a databse is obtained.
+ * </li>
+ * <li> Drop all database tables that are related to the persistence unit under test, including JPA provider
+ * specific tables.
+ * </li>
+ * <li> Creating a datasource for the database and make the datasource available through JNDI.
+ * </li>
+ * <li> Creating the entity manager factory for JPA and configuring it in such a way that schema creation
+ * happens. (Typically, schema creation will be disabled in the persistence.xml but this utility enables it
+ * for unit test).
+ * </li>
+ * <li> Creating a DBUnit database tester which is appropriately configured for the persistence unit under test.
+ * </li>
+ * </ul>
+ *
+ * The main entry point for all this functionality is the {@link PersistenceUnitDescription} which describes the
+ * persistence unit and must be provided at construction of the <code>JpaTester</code>
+ *
+ * NOTE: Persistence XML files should be explicitly configured with the classes that are part of the persistence unit
+ * since scanning of classes does not work correctly in a unit test environment. This is currently the only limitation.
+ */
+public class JpaTester {
+
+ private PersistenceUnitDescription persistenceUnit;
+ private Database db;
+ private DataSource dataSource;
+ private DatabaseUtils dbUtils;
+ private JpaBuilder jpaBuilder;
+ private IDatabaseTester dbTester;
+
+ /**
+ * Constructs the tester.
+ * @param aPersistenceUnit Persistence unit under test.
+ */
+ public JpaTester(PersistenceUnitDescription aPersistenceUnit) {
+ persistenceUnit = aPersistenceUnit;
+ }
+
+ /**
+ * Starts the tester. This must be called prior to running the test.
+ * @throws Exception
+ */
+ public void start() throws Exception {
+ db = DatabaseBuilder.getDatabase();
+ dataSource = db.start();
+
+ dbUtils = new DatabaseUtils(dataSource, persistenceUnit.getTables());
+ dbUtils.dropTables();
+ dbUtils.dropTables(persistenceUnit.getJpaTables());
+
+ jpaBuilder = new JpaBuilder(dataSource, persistenceUnit);
+ jpaBuilder.start();
+
+ // db tester should be created after Jpa builder because jpa builder
+ // creates the
+ // tables that the tester looks at when it is initialized.
+ dbTester = dbUtils.createDbTester();
+ }
+
+ /**
+ * Stops the tester. This must be called after the test.
+ */
+ public void stop() {
+ if (jpaBuilder != null) {
+ jpaBuilder.stop();
+ }
+ if (db != null) {
+ db.stop();
+ }
+ }
+
+ public Database getDb() {
+ return db;
+ }
+
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
+ public IDatabaseTester getDbTester() {
+ return dbTester;
+ }
+
+ public DatabaseUtils getDbUtils() {
+ return dbUtils;
+ }
+
+ public JpaBuilder getJpaBuilder() {
+ return jpaBuilder;
+ }
+
+ public PersistenceUnitDescription getPersistenceUnit() {
+ return persistenceUnit;
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.dbunit.dataset.filter.ITableFilterSimple;
+
+public class PersistenceUnitDescription {
+
+ private String jndiName;
+ private String unitName;
+ private ITableFilterSimple tables;
+
+ public PersistenceUnitDescription(String aJndiName, String aUnitName, ITableFilterSimple aTables) {
+ jndiName = aJndiName;
+ unitName = aUnitName;
+ tables = aTables;
+ }
+
+ public String getJndiName() {
+ return jndiName;
+ }
+
+ public String getUnitName() {
+ return unitName;
+ }
+
+ public ITableFilterSimple getTables() {
+ return tables;
+ }
+
+ /**
+ * JPA provider specific tables.
+ * @return
+ */
+ public ITableFilterSimple getJpaTables() {
+ return new ToplinkTables();
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.dbunit.dataset.DataSetException;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+
+/**
+ * Toplink-specific tables.
+ */
+public class ToplinkTables implements ITableFilterSimple {
+
+ private static final List<String> TABLES = Arrays.asList(new String[] { "SEQUENCE" } );
+
+ public boolean accept(String aTableName) throws DataSetException {
+ return TABLES.contains(aTableName);
+ }
+
+}
--- /dev/null
+/**
+ * This package provide a number of utilities for database testing and in particular with
+ * JPA.
+ *
+ * The following utilities are available:
+ * <ul>
+ * <li> {@link JpaTester}: The main entry point for all JPA tests.
+ * </li>
+ * <li> {@link JpaBuilder}: A utility constructed by <code>JpaTester</code> that provides a callback based
+ * style of working with transaction-scoped entity managers.
+ * </li>
+ * <li> {@link DatabaseUtils}: A utility constructed by <code>JpaTester</code> for working with databases in general. Test code will not use this
+ * utility often.
+ * </li>
+ * <li> {@link org.dbunit.IDatabaseTester}: A DB unit database tester. The test code can use this database tester.
+ * It is also created by <code>JpaTester</code>
+ * </li>
+ * <li> {@link DatabaseBuilder}: A utility by which test code can transparently create an inmemory database or
+ * connect to an external database. This is also used by <code>JpaTester</code>
+ * </li>
+ * </ul>
+ */
+package org.wamblee.support.persistence;
--- /dev/null
+package org.wamblee.support.persistence.toplink;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+
+import oracle.toplink.essentials.jndi.JNDIConnector;
+import oracle.toplink.essentials.sessions.DatabaseLogin;
+import oracle.toplink.essentials.sessions.Session;
+import oracle.toplink.essentials.threetier.ServerSession;
+import oracle.toplink.essentials.tools.sessionconfiguration.SessionCustomizer;
+
+/**
+ * See http://wiki.eclipse.org/Customizing_the_EclipseLink_Application_(ELUG) Use for clients that would like to use a
+ * JTA SE pu instead of a RESOURCE_LOCAL SE pu.
+ *
+ * This utility also makes sure that using a persistence.xml with a JTA datasource works in a standalone Java SE
+ * environment together with our JNDI stub.
+ */
+public class JndiSessionCustomizer
+ implements SessionCustomizer {
+
+ public JndiSessionCustomizer() {
+ // Empty.
+ }
+
+ /**
+ * Get a dataSource connection and set it on the session with lookupType=STRING_LOOKUP
+ */
+ public void customize(Session session) throws Exception {
+ JNDIConnector connector = null;
+ Context context = null;
+ try {
+ context = new InitialContext();
+ if(null != context) {
+ connector = (JNDIConnector)session.getLogin().getConnector(); // possible CCE
+ // Change from COMPOSITE_NAME_LOOKUP to STRING_LOOKUP
+ // Note: if both jta and non-jta elements exist this will only change the first one - and may still result in
+ // the COMPOSITE_NAME_LOOKUP being set
+ // Make sure only jta-data-source is in persistence.xml with no non-jta-data-source property set
+ connector.setLookupType(JNDIConnector.STRING_LOOKUP);
+
+ // Or, if you are specifying both JTA and non-JTA in your persistence.xml then set both connectors to be safe
+ JNDIConnector writeConnector = (JNDIConnector)session.getLogin().getConnector();
+ writeConnector.setLookupType(JNDIConnector.STRING_LOOKUP);
+ JNDIConnector readConnector =
+ (JNDIConnector)((DatabaseLogin)((ServerSession)session).getReadConnectionPool().getLogin()).getConnector();
+ readConnector.setLookupType(JNDIConnector.STRING_LOOKUP);
+
+ System.out.println("JndiSessionCustomizer: configured " + connector.getName());
+ }
+ else {
+ throw new Exception("JndiSessionCustomizer: Context is null");
+ }
+ }
+ catch(Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+org.wamblee.support.persistence.DerbyDatabaseProvider
+org.wamblee.support.persistence.ExternalDatabaseProvider
--- /dev/null
+package org.wamblee.support.jndi;
+
+import static junit.framework.Assert.assertEquals;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.jndi.StubInitialContextFactory;
+
+public class StubInitiaContextFactoryTest {
+
+ @Before
+ @After
+ public void setUp() {
+ StubInitialContextFactory.unregister();
+ }
+
+
+ @Test(expected = NamingException.class)
+ public void testLookupNotRegistered() throws Exception {
+ InitialContext ctx = new InitialContext();
+ ctx.bind("a/b", "hallo");
+ }
+
+ @Test
+ public void testLookup() throws Exception {
+ StubInitialContextFactory.register();
+
+ InitialContext ctx = new InitialContext();
+ ctx.bind("a/b", "hallo");
+
+ ctx = new InitialContext();
+ Object obj = ctx.lookup("a/b");
+
+ assertEquals("hallo", obj);
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import org.junit.Test;
+import org.wamblee.support.persistence.DatabaseBuilder;
+import org.wamblee.support.persistence.DatabaseDescription;
+
+public class DatabaseBuilderTest {
+
+
+ @Test
+ public void testListAvailableDatabases() {
+ for (DatabaseDescription description: DatabaseBuilder.getSupportedDatabases()) {
+ System.out.println(description);
+ }
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import static junit.framework.Assert.assertEquals;
+
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+
+import org.dbunit.IDatabaseTester;
+import org.dbunit.dataset.ITable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.Database;
+import org.wamblee.support.persistence.DatabaseBuilder;
+import org.wamblee.support.persistence.DatabaseUtils;
+import org.wamblee.support.persistence.JpaBuilder;
+import org.wamblee.support.persistence.PersistenceUnitDescription;
+import org.wamblee.support.persistence.JpaBuilder.JpaUnitOfWork;
+
+public class DatabaseUtilsTest {
+ private Database db;
+ private DataSource dataSource;
+ private PersistenceUnitDescription persistenceUnit;
+ private JpaBuilder builder;
+ private DatabaseUtils dbutils;
+ private IDatabaseTester dbtester;
+
+ @Before
+ public void setUp() throws Exception {
+ db = DatabaseBuilder.getDatabase();
+ dataSource = db.start();
+
+ persistenceUnit = new MyPersistenceUnit();
+
+ dbutils = new DatabaseUtils(dataSource, persistenceUnit.getTables());
+ dbutils.dropTables();
+ dbutils.dropTables(persistenceUnit.getJpaTables());
+
+ builder = new JpaBuilder(dataSource, persistenceUnit);
+ builder.start();
+
+ dbtester = dbutils.createDbTester();
+ }
+
+ @After
+ public void tearDown() {
+ builder.stop();
+ db.stop();
+ }
+
+ @Test
+ public void testTablesCorrect() throws Exception {
+ String[] tables = dbutils.getTableNames();
+ assertEquals(1, tables.length);
+ assertEquals("XYZ_MYENTITY", tables[0]);
+ }
+
+ @Test
+ public void testDeleteTables() throws Exception {
+ String[] tables = dbutils.getTableNames();
+ assertEquals(1, tables.length);
+ assertEquals("XYZ_MYENTITY", tables[0]);
+
+ // Put some data in the database.
+ builder.execute(new JpaUnitOfWork<Void>() {
+ public Void execute(EntityManager em) {
+ MyEntity entity = new MyEntity("a", "b");
+ em.persist(entity);
+ return null;
+ }
+ });
+
+ // Verify one row is written (using Db unit)
+ ITable table = dbtester.getDataSet().getTable("XYZ_MYENTITY");
+ assertEquals(1, table.getRowCount());
+
+ // Clean the database
+ dbutils.cleanDatabase();
+ table = dbtester.getDataSet().getTable("XYZ_MYENTITY");
+ assertEquals(0, table.getRowCount());
+
+ // Now drop the database
+ dbutils.dropTables();
+ dbutils.dropTables(persistenceUnit.getJpaTables());
+ tables = dbutils.getTableNames();
+ assertEquals(0, tables.length);
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.sql.Connection;
+
+import javax.sql.DataSource;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.Database;
+import org.wamblee.support.persistence.DatabaseBuilder;
+
+
+public class DerbyDatabaseTest {
+
+ private Database db;
+ private DataSource ds;
+
+ @Before
+ public void setUp() {
+ db = DatabaseBuilder.getDatabase();
+ ds = db.start();
+ }
+
+ @After
+ public void tearDown() {
+ db.stop();
+ }
+
+ @Test
+ public void testConnect() throws Exception {
+ Connection conn = ds.getConnection();
+ try {
+ System.out.println("Database name: " + conn.getMetaData().getDatabaseProductName());
+ } finally {
+ conn.close();
+ }
+ }
+
+ @Test
+ public void testUseASecondTimeInTheSameTestCase() throws Exception {
+ testConnect();
+ tearDown();
+ setUp();
+ testConnect();
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.sql.Connection;
+
+import javax.sql.DataSource;
+
+import org.junit.Test;
+import org.wamblee.support.persistence.Database;
+import org.wamblee.support.persistence.DatabaseBuilder;
+import org.wamblee.support.persistence.DatabaseProvider;
+import org.wamblee.support.persistence.ExternalDatabase;
+
+import static junit.framework.TestCase.*;
+
+public class ExternalDatabaseTest {
+
+ @Test
+ public void testExternalDB() throws Exception {
+ // Connect to inmemory db using External database class.
+
+ Database inmemory = DatabaseBuilder
+ .getDatabase(DatabaseProvider.CAPABILITY_IN_MEMORY);
+ try {
+ inmemory.start();
+
+ System.setProperty(ExternalDatabase.DB_URL_PROP, inmemory
+ .getExternalJdbcUrl());
+ System.setProperty(ExternalDatabase.DB_USER_PROP, inmemory
+ .getUsername());
+ System.setProperty(ExternalDatabase.DB_PASSWORD_PROP, inmemory
+ .getPassword());
+
+ Database external = DatabaseBuilder
+ .getDatabase(DatabaseProvider.CAPABILITY_EXTERNAL);
+ assertTrue(external instanceof ExternalDatabase);
+ try {
+ DataSource ds = external.start();
+ Connection conn = ds.getConnection();
+ try {
+ System.out.println("Database name: "
+ + conn.getMetaData().getDatabaseProductName());
+ } finally {
+ conn.close();
+ }
+ } finally {
+ external.stop();
+ }
+ } finally {
+ inmemory.stop();
+ }
+
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "XYZ_MYENTITY")
+public class MyEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ private String sleuteltje;
+ private String value;
+
+
+ public MyEntity() {
+ // Empty
+ }
+
+ public MyEntity(String aKey, String aValue) {
+ sleuteltje = aKey;
+ value = aValue;
+ }
+
+ public String getKey() {
+ return sleuteltje;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Persistence;
+import javax.sql.DataSource;
+
+import org.dbunit.DataSourceDatabaseTester;
+import org.dbunit.DatabaseTestCase;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.dataset.ITable;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.DatabaseUtils;
+import org.wamblee.support.persistence.JpaBuilder;
+import org.wamblee.support.persistence.JpaTester;
+import org.wamblee.support.persistence.JpaBuilder.JpaUnitOfWork;
+
+import static junit.framework.Assert.*;
+
+
+/**
+ * This class shows an example of how to test an entity using jpa.
+ */
+public class MyEntityExampleTest {
+
+ // This is the magical object that does all the test setup.
+ private JpaTester jpaTester;
+
+ // The jpa tester initializes a lot for us....
+
+ // A JPA builder that provides a transaction scoped entity manager for us.
+ private JpaBuilder builder;
+
+ // The database tester for dbunit which is appropriately configured for our persistence unit.
+ private IDatabaseTester dbtester;
+
+ // Database utilities with some additional functionality for working with the databse
+ // such as dropping tables, cleaning tables, etc.
+ private DatabaseUtils dbutils;
+
+ @Before
+ public void setUp() throws Exception {
+
+ // First we create the JpaTester by telling us which persistence unit we are going to test
+ jpaTester = new JpaTester(new MyPersistenceUnit());
+ jpaTester.start();
+
+ // Retrieve some useful objects fromt he jpa tester. It also provides direct access to the datasource
+ // but we don't need it. We can use datbase utils if we want to execute straight JDBC calls.
+ builder = jpaTester.getJpaBuilder();
+ dbtester = jpaTester.getDbTester();
+ dbutils = jpaTester.getDbUtils();
+ }
+
+ @After
+ public void tearDown() {
+ jpaTester.stop();
+ }
+
+ @Test
+ public void testEntityPersistence() throws Exception {
+
+ // Use the JPA builder to create a transaction scoped entity manager for as and execute the
+ // unit of work.
+ builder.execute(new JpaUnitOfWork<Void>() {
+ public Void execute(EntityManager em) {
+ MyEntity entity = new MyEntity("a", "b");
+ em.persist(entity);
+ return null;
+ }
+ });
+
+ // Verify one row is written (using Db unit)
+ ITable table = dbtester.getDataSet().getTable("XYZ_MYENTITY");
+ assertEquals(1, table.getRowCount());
+
+ assertEquals("a", table.getValue(0, "SLEUTELTJE"));
+ assertEquals("b", table.getValue(0, "VALUE"));
+
+ // For this simple test, it can also be done through DatabaseUtils
+ assertEquals(1, dbutils.getTableSize("XYZ_MYENTITY"));
+
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import org.wamblee.support.persistence.PersistenceUnitDescription;
+
+/**
+ * This class describes the persistence unit that we are testing.
+ */
+public class MyPersistenceUnit extends PersistenceUnitDescription {
+ private static final String JNDI_NAME = "wamblee/support/test";
+ private static final String PERSISTENCE_UNIT_NAME = "org.wamblee.jee.support-test";
+
+ public MyPersistenceUnit() {
+ super(JNDI_NAME, PERSISTENCE_UNIT_NAME, new MyTables());
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import org.dbunit.dataset.DataSetException;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+
+/**
+ * This class describes the tables we are testing.
+ * This implementation selects all tables with the "XYZ" prefix.
+ */
+public class MyTables implements ITableFilterSimple {
+
+ /**
+ *
+ * @param aJpaTables Specific tables used by the JPA provider in addition to those for the
+ * entities.
+ */
+ public MyTables() {
+ // Empty.
+ }
+
+ public boolean accept(String tableName) throws DataSetException {
+ return tableName.startsWith("XYZ_");
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<persistence version="1.0"
+ xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
+ <persistence-unit name="org.wamblee.jee.support-test"
+ transaction-type="JTA">
+ <jta-data-source>wamblee/support/test</jta-data-source>
+ <class>org.wamblee.support.persistence.MyEntity</class>
+ <properties>
+ <!--
+ <property name="toplink.ddl-generation"
+ value="drop-and-create-tables" />
+ -->
+ <!-- <property name="toplink.ddl-generation" value="none" /> -->
+ <property name="toplink.ddl-generation" value="create-tables" />
+
+ <!--
+ <property name="toplink.create-ddl-jdbc-file-name"
+ value="create_tm_ddl.jdbc" />
+ -->
+ <!--
+ <property name="toplink.drop-ddl-jdbc-file-name"
+ value="drop_tm_ddl.jdbc" />
+ -->
+
+ <property name="toplink.logging.level" value="INFO" />
+
+ <!--
+ Toplink 2nd level cache disable for JSROuteRule, this is required so
+ that changes made by one instance in a cluster become visible to the
+ cluster. See
+ http://www.oracle.com/technology/products/ias/toplink/JPA/essentials/toplink-jpa-extensions.html#TopLinkCaching
+ for information on these toplink caching properties.
+ -->
+ <property name="toplink.cache.shared.default" value="false" />
+
+ </properties>
+
+ </persistence-unit>
+</persistence>