Preliminary version of JPA test support.
authorerik <erik@77661180-640e-0410-b3a8-9f9b13e6d0e0>
Sat, 3 Apr 2010 20:19:37 +0000 (20:19 +0000)
committererik <erik@77661180-640e-0410-b3a8-9f9b13e6d0e0>
Sat, 3 Apr 2010 20:19:37 +0000 (20:19 +0000)
Working now again.

31 files changed:
trunk/support/test/pom.xml [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/jndi/StubInitialContext.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/jndi/StubInitialContextFactory.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/AbstractDatabase.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/AbstractDatabaseProvider.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/Database.java [new file with mode: 0755]
trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseBuilder.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseDescription.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseProvider.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseUtils.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/DerbyDatabase.java [new file with mode: 0755]
trunk/support/test/src/main/java/org/wamblee/support/persistence/DerbyDatabaseProvider.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/ExternalDatabase.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/ExternalDatabaseProvider.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/JpaBuilder.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/JpaTester.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/PersistenceUnitDescription.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/ToplinkTables.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/package-info.java [new file with mode: 0644]
trunk/support/test/src/main/java/org/wamblee/support/persistence/toplink/JndiSessionCustomizer.java [new file with mode: 0644]
trunk/support/test/src/main/resources/META-INF/services/org.wamblee.support.persistence.DatabaseProvider [new file with mode: 0644]
trunk/support/test/src/test/java/org/wamblee/support/jndi/StubInitiaContextFactoryTest.java [new file with mode: 0644]
trunk/support/test/src/test/java/org/wamblee/support/persistence/DatabaseBuilderTest.java [new file with mode: 0644]
trunk/support/test/src/test/java/org/wamblee/support/persistence/DatabaseUtilsTest.java [new file with mode: 0644]
trunk/support/test/src/test/java/org/wamblee/support/persistence/DerbyDatabaseTest.java [new file with mode: 0644]
trunk/support/test/src/test/java/org/wamblee/support/persistence/ExternalDatabaseTest.java [new file with mode: 0644]
trunk/support/test/src/test/java/org/wamblee/support/persistence/MyEntity.java [new file with mode: 0644]
trunk/support/test/src/test/java/org/wamblee/support/persistence/MyEntityExampleTest.java [new file with mode: 0644]
trunk/support/test/src/test/java/org/wamblee/support/persistence/MyPersistenceUnit.java [new file with mode: 0644]
trunk/support/test/src/test/java/org/wamblee/support/persistence/MyTables.java [new file with mode: 0644]
trunk/support/test/src/test/resources/META-INF/persistence.xml [new file with mode: 0644]

diff --git a/trunk/support/test/pom.xml b/trunk/support/test/pom.xml
new file mode 100644 (file)
index 0000000..5ab92ab
--- /dev/null
@@ -0,0 +1,80 @@
+<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>
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/jndi/StubInitialContext.java b/trunk/support/test/src/main/java/org/wamblee/support/jndi/StubInitialContext.java
new file mode 100644 (file)
index 0000000..a7e4c96
--- /dev/null
@@ -0,0 +1,31 @@
+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());
+       }
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/jndi/StubInitialContextFactory.java b/trunk/support/test/src/main/java/org/wamblee/support/jndi/StubInitialContextFactory.java
new file mode 100644 (file)
index 0000000..c7e4e2f
--- /dev/null
@@ -0,0 +1,63 @@
+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;
+       }
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/AbstractDatabase.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/AbstractDatabase.java
new file mode 100644 (file)
index 0000000..59f3d77
--- /dev/null
@@ -0,0 +1,78 @@
+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");
+       }
+
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/AbstractDatabaseProvider.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/AbstractDatabaseProvider.java
new file mode 100644 (file)
index 0000000..6ed9d6c
--- /dev/null
@@ -0,0 +1,18 @@
+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; 
+       }
+
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/Database.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/Database.java
new file mode 100755 (executable)
index 0000000..56122ed
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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();
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseBuilder.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseBuilder.java
new file mode 100644 (file)
index 0000000..9c599a0
--- /dev/null
@@ -0,0 +1,116 @@
+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);
+               }
+       }
+
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseDescription.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseDescription.java
new file mode 100644 (file)
index 0000000..c31427b
--- /dev/null
@@ -0,0 +1,41 @@
+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; 
+       }
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseProvider.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseProvider.java
new file mode 100644 (file)
index 0000000..f6736aa
--- /dev/null
@@ -0,0 +1,42 @@
+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();
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseUtils.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/DatabaseUtils.java
new file mode 100644 (file)
index 0000000..781764b
--- /dev/null
@@ -0,0 +1,394 @@
+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;
+       }
+
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/DerbyDatabase.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/DerbyDatabase.java
new file mode 100755 (executable)
index 0000000..646931c
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * 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);
+       }
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/DerbyDatabaseProvider.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/DerbyDatabaseProvider.java
new file mode 100644 (file)
index 0000000..088202c
--- /dev/null
@@ -0,0 +1,32 @@
+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;
+    }
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/ExternalDatabase.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/ExternalDatabase.java
new file mode 100644 (file)
index 0000000..2533df4
--- /dev/null
@@ -0,0 +1,82 @@
+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. 
+       }
+
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/ExternalDatabaseProvider.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/ExternalDatabaseProvider.java
new file mode 100644 (file)
index 0000000..871be11
--- /dev/null
@@ -0,0 +1,35 @@
+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);
+       }
+
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/JpaBuilder.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/JpaBuilder.java
new file mode 100644 (file)
index 0000000..b46ffa3
--- /dev/null
@@ -0,0 +1,134 @@
+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();
+               }
+       }
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/JpaTester.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/JpaTester.java
new file mode 100644 (file)
index 0000000..c0a22cd
--- /dev/null
@@ -0,0 +1,113 @@
+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;
+       }
+
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/PersistenceUnitDescription.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/PersistenceUnitDescription.java
new file mode 100644 (file)
index 0000000..502f43d
--- /dev/null
@@ -0,0 +1,40 @@
+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(); 
+       }
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/ToplinkTables.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/ToplinkTables.java
new file mode 100644 (file)
index 0000000..4d45d0f
--- /dev/null
@@ -0,0 +1,20 @@
+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);
+       }
+
+}
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/package-info.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/package-info.java
new file mode 100644 (file)
index 0000000..1bd7050
--- /dev/null
@@ -0,0 +1,23 @@
+/**
+ * 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;
diff --git a/trunk/support/test/src/main/java/org/wamblee/support/persistence/toplink/JndiSessionCustomizer.java b/trunk/support/test/src/main/java/org/wamblee/support/persistence/toplink/JndiSessionCustomizer.java
new file mode 100644 (file)
index 0000000..814fe59
--- /dev/null
@@ -0,0 +1,59 @@
+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
diff --git a/trunk/support/test/src/main/resources/META-INF/services/org.wamblee.support.persistence.DatabaseProvider b/trunk/support/test/src/main/resources/META-INF/services/org.wamblee.support.persistence.DatabaseProvider
new file mode 100644 (file)
index 0000000..62979fb
--- /dev/null
@@ -0,0 +1,2 @@
+org.wamblee.support.persistence.DerbyDatabaseProvider
+org.wamblee.support.persistence.ExternalDatabaseProvider
diff --git a/trunk/support/test/src/test/java/org/wamblee/support/jndi/StubInitiaContextFactoryTest.java b/trunk/support/test/src/test/java/org/wamblee/support/jndi/StubInitiaContextFactoryTest.java
new file mode 100644 (file)
index 0000000..6016e2b
--- /dev/null
@@ -0,0 +1,41 @@
+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);
+       }
+       
+}
diff --git a/trunk/support/test/src/test/java/org/wamblee/support/persistence/DatabaseBuilderTest.java b/trunk/support/test/src/test/java/org/wamblee/support/persistence/DatabaseBuilderTest.java
new file mode 100644 (file)
index 0000000..5136487
--- /dev/null
@@ -0,0 +1,16 @@
+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);
+               }
+       }
+}
diff --git a/trunk/support/test/src/test/java/org/wamblee/support/persistence/DatabaseUtilsTest.java b/trunk/support/test/src/test/java/org/wamblee/support/persistence/DatabaseUtilsTest.java
new file mode 100644 (file)
index 0000000..e57f01a
--- /dev/null
@@ -0,0 +1,89 @@
+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);
+    }
+
+}
diff --git a/trunk/support/test/src/test/java/org/wamblee/support/persistence/DerbyDatabaseTest.java b/trunk/support/test/src/test/java/org/wamblee/support/persistence/DerbyDatabaseTest.java
new file mode 100644 (file)
index 0000000..ffc17ae
--- /dev/null
@@ -0,0 +1,47 @@
+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();
+       }
+}
diff --git a/trunk/support/test/src/test/java/org/wamblee/support/persistence/ExternalDatabaseTest.java b/trunk/support/test/src/test/java/org/wamblee/support/persistence/ExternalDatabaseTest.java
new file mode 100644 (file)
index 0000000..dbe5800
--- /dev/null
@@ -0,0 +1,53 @@
+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();
+               }
+
+       }
+}
diff --git a/trunk/support/test/src/test/java/org/wamblee/support/persistence/MyEntity.java b/trunk/support/test/src/test/java/org/wamblee/support/persistence/MyEntity.java
new file mode 100644 (file)
index 0000000..c2b4728
--- /dev/null
@@ -0,0 +1,39 @@
+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;
+       }
+
+       
+}
diff --git a/trunk/support/test/src/test/java/org/wamblee/support/persistence/MyEntityExampleTest.java b/trunk/support/test/src/test/java/org/wamblee/support/persistence/MyEntityExampleTest.java
new file mode 100644 (file)
index 0000000..e7b86fc
--- /dev/null
@@ -0,0 +1,87 @@
+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"));
+       
+       }
+
+}
diff --git a/trunk/support/test/src/test/java/org/wamblee/support/persistence/MyPersistenceUnit.java b/trunk/support/test/src/test/java/org/wamblee/support/persistence/MyPersistenceUnit.java
new file mode 100644 (file)
index 0000000..777dd88
--- /dev/null
@@ -0,0 +1,15 @@
+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());
+       }
+}
diff --git a/trunk/support/test/src/test/java/org/wamblee/support/persistence/MyTables.java b/trunk/support/test/src/test/java/org/wamblee/support/persistence/MyTables.java
new file mode 100644 (file)
index 0000000..1283a8f
--- /dev/null
@@ -0,0 +1,25 @@
+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_");
+       }
+
+}
diff --git a/trunk/support/test/src/test/resources/META-INF/persistence.xml b/trunk/support/test/src/test/resources/META-INF/persistence.xml
new file mode 100644 (file)
index 0000000..c252881
--- /dev/null
@@ -0,0 +1,40 @@
+<?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>