Skip to content

Commit

Permalink
Add DataSource support (GH #24)
Browse files Browse the repository at this point in the history
Implement a DataSource factory
Add additional unit testing

Resolves #24

Change-Id: Ic1123f4b136b71e7120c6ca3c0cef7d915edd0e1
  • Loading branch information
bindul committed Feb 26, 2016
1 parent cba46a5 commit 58248a6
Show file tree
Hide file tree
Showing 11 changed files with 647 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2016 Development Entropy (deventropy.org) Contributors
*
* 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.deventropy.junithelper.derby;

import javax.sql.ConnectionPoolDataSource;
import javax.sql.DataSource;
import javax.sql.XADataSource;

/**
* Factory for creating {@link DataSource} instances to connect to a Derby resource. The specific resources provide
* implementations to this factory. Typically all three kinds of <code>DataSource</code>s ({@link DataSource},
* {@link ConnectionPoolDataSource} and {@link XADataSource}) are supported by the factory instances.
*
* <p>Factory implementations can support caching of the data sources created; and cached instances of the datasource
* are retrieved by setting the <code>cachedInstance</code> parameter to <code>true</code>. If an implementation does
* not support caching, this parameter is simply ignored and a new instance is returned every time.
*
* <p>Derby resources may optionally check if the derby instance is active, and if so may throw
* {@link IllegalStateException} if the <code>DataSource</code> factory methods are invoked while the underlying
* instance is not active.
*
* @see <a href="https://db.apache.org/derby/docs/10.12/ref/rrefapi1003363.html">Derby DataSource classes</a>
*
* @author Bindul Bhowmik
*/
public interface EmbeddedDerbyDataSourceFactory {

/**
* Returns a new or cached {@link DataSource} instance with parameters set up to create connections to connect to
* the underlying Derby instance.
*
* <p>For the embedded Derby instance, tha actual instance returned is usually
* {@link org.apache.derby.jdbc.EmbeddedDataSource}.
*
* @param cachedInstance Should a cached instance be returned if available and caching is enabled.
* @return A DataSource instance
* @throws IllegalStateException If the underlying implementation does state checking and the resource is not active
*/
DataSource getDataSource (final boolean cachedInstance);

/**
* Returns a new or cached {@link ConnectionPoolDataSource} instance with parameters set up to create connections to
* connect to the underlying Derby instance.
*
* <p>For the embedded Derby instance, tha actual instance returned is usually
* {@link org.apache.derby.jdbc.EmbeddedConnectionPoolDataSource}.
*
* @param cachedInstance Should a cached instance be returned if available and caching is enabled.
* @return A ConnectionPoolDataSource instance
* @throws IllegalStateException If the underlying implementation does state checking and the resource is not active
*/
ConnectionPoolDataSource getConnectionPoolDataSource (final boolean cachedInstance);

/**
* Returns a new or cached {@link XADataSource} instance with parameters set up to create connections to
* connect to the underlying Derby instance.
*
* <p>For the embedded Derby instance, tha actual instance returned is usually
* {@link org.apache.derby.jdbc.EmbeddedXADataSource}.
*
* @param cachedInstance Should a cached instance be returned if available and caching is enabled.
* @return A XADataSource instance
* @throws IllegalStateException If the underlying implementation does state checking and the resource is not active
*/
XADataSource getXADataSource (final boolean cachedInstance);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.ConnectionPoolDataSource;
import javax.sql.DataSource;
import javax.sql.XADataSource;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.derby.jdbc.EmbeddedConnectionPoolDataSource;
import org.apache.derby.jdbc.EmbeddedDataSource;
import org.apache.derby.jdbc.EmbeddedDataSourceInterface;
import org.apache.derby.jdbc.EmbeddedXADataSource;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand Down Expand Up @@ -102,6 +110,7 @@ public class EmbeddedDerbyResource extends ExternalResource implements Closeable
private String oldDerbySystemHomeValue;

private final DerbyBackupOperationsHelper backupOperationsHelper = new DerbyBackupOperationsHelper(this);
private final EmbeddedDerbyDataSourceFactory dataSourceFactory = new EmbeddedDerbyDataSourceFactoryImpl();

/**
* Creates a new Derby resource. All configurable parameters for this resource come from the config object
Expand Down Expand Up @@ -140,12 +149,16 @@ public EmbeddedDerbyResource (final DerbyResourceConfig dbResourceConfig,

private String buildJdbcUrl () {
final StringBuilder jdbcUrlBldr = new StringBuilder().append(config.getSubSubProtocol().jdbcConnectionPrefix());
appendDbLocNameToUrl(jdbcUrlBldr);
return jdbcUrlBldr.toString();
}

private void appendDbLocNameToUrl (final StringBuilder jdbcUrlBldr) {
if (JdbcDerbySubSubProtocol.Jar == config.getSubSubProtocol()) {
// for :jar: protocol, see http://db.apache.org/derby/docs/10.12/devguide/cdevdeploy11201.html
jdbcUrlBldr.append('(').append(config.getJarDatabaseJarFile()).append(')');
}
jdbcUrlBldr.append(config.getDatabasePath());
return jdbcUrlBldr.toString();
}

/* (non-Javadoc)
Expand Down Expand Up @@ -176,52 +189,63 @@ public void start () throws IOException, SQLException {
return; // already started
}

// Validate and setup
if (null != derbySystemHomeParent) {
this.derbySystemHome = derbySystemHomeParent.newFolder();
}
FileUtils.forceMkdir(derbySystemHome);
oldDerbySystemHomeValue = System.getProperty(DerbyConstants.PROP_DERBY_SYSTEM_HOME); // Save it to reset later
System.setProperty(DerbyConstants.PROP_DERBY_SYSTEM_HOME, derbySystemHome.getAbsolutePath());
setupDerbyProperties();

// Start the database
// Recommended Derby startup process,
// see https://db.apache.org/derby/docs/10.12/publishedapi/org/apache/derby/jdbc/EmbeddedDriver.html
Connection conn = null;
try {
Class.forName(DerbyConstants.DERBY_EMBEDDED_DRIVER_CLASS).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
// Validate and setup
if (null != derbySystemHomeParent) {
this.derbySystemHome = derbySystemHomeParent.newFolder();
}
FileUtils.forceMkdir(derbySystemHome);
// Save it to reset later
oldDerbySystemHomeValue = System.getProperty(DerbyConstants.PROP_DERBY_SYSTEM_HOME);
System.setProperty(DerbyConstants.PROP_DERBY_SYSTEM_HOME, derbySystemHome.getAbsolutePath());
setupDerbyProperties();

// Start the database
// Recommended Derby startup process,
// see https://db.apache.org/derby/docs/10.12/publishedapi/org/apache/derby/jdbc/EmbeddedDriver.html
try {
Class.forName(DerbyConstants.DERBY_EMBEDDED_DRIVER_CLASS).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new SQLException(
"Unable to initialize Derby driver class: " + DerbyConstants.DERBY_EMBEDDED_DRIVER_CLASS, e);
}
// Create / Connect to the database
conn = DriverManager.getConnection(buildCreateJDBCUrl());
isActive = true;
} catch (IOException | SQLException e) {
// Reset the Derby System Home property
resetDerbyHome();
throw new SQLException(
"Unable to initialize Derby driver class: " + DerbyConstants.DERBY_EMBEDDED_DRIVER_CLASS, e);
}
// Create / Connect to the database
final Connection conn = DriverManager.getConnection(buildCreateJDBCUrl());
isActive = true;
try {
// Post init scripts
executePostInitScripts(conn);
throw e;
} finally {
DerbyUtils.closeQuietly(conn);
}

// Post init scripts
executePostInitScripts();
}

private void executePostInitScripts (final Connection conn) throws IOException {
final DerbyScriptRunner scriptRunner = new DerbyScriptRunner(conn);
for (String postInitScript : config.getPostInitScripts()) {
final File scriptLogFile = new File(derbySystemHome, "post-init-"
+ postInitScript.replaceAll("/", "_") + ".log");
try {
final int result = scriptRunner.executeScript(postInitScript, scriptLogFile);
if (result != 0) {
private void executePostInitScripts () throws IOException, SQLException {
Connection conn = null;
try {
conn = createConnection();
final DerbyScriptRunner scriptRunner = new DerbyScriptRunner(conn);
for (String postInitScript : config.getPostInitScripts()) {
final File scriptLogFile = new File(derbySystemHome, "post-init-"
+ postInitScript.replaceAll("/", "_") + ".log");
try {
final int result = scriptRunner.executeScript(postInitScript, scriptLogFile);
if (result != 0) {
log.warn(FileUtils.readFileToString(scriptLogFile));
throw new IOException("Exceptions exist in script. See output for details");
}
} catch (IOException e) {
log.warn(FileUtils.readFileToString(scriptLogFile));
throw new IOException("Exceptions exist in script. See output for details");
}
} catch (IOException e) {
log.warn(FileUtils.readFileToString(scriptLogFile));
throw new IOException("Exceptions exist in script. See output for details");
}
} finally {
DerbyUtils.closeQuietly(conn);
}
}

Expand Down Expand Up @@ -418,4 +442,94 @@ public void backupLiveDatabase (final File backupDir, final boolean waitForTrans
backupOperationsHelper.backupLiveDatabase(backupDir, waitForTransactions, enableArchiveLogging,
deleteArchivedLogs);
}

/**
* Returns the {@link EmbeddedDerbyDataSourceFactory} instance for this resource from which data sources can be
* created / cached. The factory returned supports caching <code>ataSource</code>s created. It also checks the
* state of <code>this</code> instance and will throw {@link IllegalStateException} if the resource is not
* {@link #isActive()}.
*
* @return Factory to create data sources for this instance.
*/
public EmbeddedDerbyDataSourceFactory getDataSourceFactory () {
return dataSourceFactory;
}

private class EmbeddedDerbyDataSourceFactoryImpl implements EmbeddedDerbyDataSourceFactory {

private EmbeddedDataSource embeddedDataSource;
private EmbeddedConnectionPoolDataSource embeddedConnectionPoolDataSource;
private EmbeddedXADataSource embeddedXADataSource;

/* (non-Javadoc)
* @see org.deventropy.junithelper.derby.EmbeddedDerbyDataSourceFactory#getDataSource(boolean)
*/
@Override
public DataSource getDataSource (final boolean cachedInstance) {
ensureActive();
if (cachedInstance) {
if (null == embeddedDataSource) {
embeddedDataSource = createEmbeddedDataSource();
}
return embeddedDataSource;
}
return createEmbeddedDataSource();
}

private EmbeddedDataSource createEmbeddedDataSource () {
final EmbeddedDataSource embeddedDs = new EmbeddedDataSource();
setupDataSource(embeddedDs);
return embeddedDs;
}

/* (non-Javadoc)
* @see org.deventropy.junithelper.derby.EmbeddedDerbyDataSourceFactory#getConnectionPoolDataSource(boolean)
*/
@Override
public ConnectionPoolDataSource getConnectionPoolDataSource (final boolean cachedInstance) {
ensureActive();
if (cachedInstance) {
if (null == embeddedConnectionPoolDataSource) {
embeddedConnectionPoolDataSource = createEmbeddedConnectionPoolDataSource();
}
return embeddedConnectionPoolDataSource;
}
return createEmbeddedConnectionPoolDataSource();
}

private EmbeddedConnectionPoolDataSource createEmbeddedConnectionPoolDataSource () {
final EmbeddedConnectionPoolDataSource connectionPoolDataSource = new EmbeddedConnectionPoolDataSource();
setupDataSource(connectionPoolDataSource);
return connectionPoolDataSource;
}

/* (non-Javadoc)
* @see org.deventropy.junithelper.derby.EmbeddedDerbyDataSourceFactory#getXADataSource(boolean)
*/
@Override
public XADataSource getXADataSource (final boolean cachedInstance) {
ensureActive();
if (cachedInstance) {
if (null == embeddedXADataSource) {
embeddedXADataSource = createEmbeddedXADataSource();
}
return embeddedXADataSource;
}
return createEmbeddedXADataSource();
}

private EmbeddedXADataSource createEmbeddedXADataSource () {
final EmbeddedXADataSource xaDataSource = new EmbeddedXADataSource();
setupDataSource(xaDataSource);
return xaDataSource;
}

private void setupDataSource (final EmbeddedDataSourceInterface dataSource) {
final StringBuilder dsDatabaseName = new StringBuilder()
.append(config.getSubSubProtocol().datasourceDatabaseNamePrefix());
appendDbLocNameToUrl(dsDatabaseName);
dataSource.setDatabaseName(dsDatabaseName.toString());
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ public enum JdbcDerbySubSubProtocol {
Classpath ("classpath");

private final String jdbcConnectionPrefix;
private final String datasourceDatabaseNamePrefix;

JdbcDerbySubSubProtocol (final String subprotocolcode) {
jdbcConnectionPrefix = DerbyConstants.DERBY_JDBC_URL_PREFIX + subprotocolcode + ":";
datasourceDatabaseNamePrefix = subprotocolcode + ":";
}

/**
Expand All @@ -58,4 +60,14 @@ public enum JdbcDerbySubSubProtocol {
public String jdbcConnectionPrefix () {
return jdbcConnectionPrefix;
}

/**
* Returns the database name prefix (with the sub-sub protocol) as required to set up a datasource for the database.
* This does not include the {@value DerbyConstants#DERBY_JDBC_URL_PREFIX} prefix.
*
* @return The database name prefix
*/
public String datasourceDatabaseNamePrefix () {
return datasourceDatabaseNamePrefix;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
* which can be added to a test class using the {@link org.junit.Rule} or {@link org.junit.ClassRule} annotation,
* with initialization scripts; and JUnit managing the initialization and de-initialization of the database instance.
*
* <p>Embedded instances can be accessed using standard JDBC connections created through the
* {@link java.sql.DriverManager}, and convenience methods are provided to get the JDBC connection URL (see
* {@link org.deventropy.junithelper.derby.EmbeddedDerbyResource#getJdbcUrl()}. The implementation also supports
* creating {@link javax.sql.DataSource}s to connect to the database. An
* {@link org.deventropy.junithelper.derby.EmbeddedDerbyDataSourceFactory} is returned by the resource in the
* {@link org.deventropy.junithelper.derby.EmbeddedDerbyResource#getDataSourceFactory()} method to enable creating
* datasources.
*
* <p>The runtime instance can be configured using the {@link org.deventropy.junithelper.derby.DerbyResourceConfig}.
*
* <p>The library also comes with some utilities to manage the running instance
Expand Down

0 comments on commit 58248a6

Please sign in to comment.