DerbyResourceConfig.java

/* 
 * Copyright 2015 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 java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

import org.deventropy.shared.utils.ArgumentCheck;

/**
 * Configurations to control the {@link EmbeddedDerbyResource}.
 * 
 * <p>This class provides a fluid interface to build the config object to pass to the constructor of the resource.
 * Example usage:
 * 
 * <pre>
 * EmbeddedDerbyResource derbyResource = new EmbeddedDerbyResource(
 * 		DerbyResourceConfig.buildDefault().);
 * </pre>
 * 
 * <p>
 * Some methods in this class, for example the methods setting the subSubProtocol, can be mutually exclusive and unset /
 * alter values set by other methods. Certain configuration values have valid values as <code>null</code> and do not
 * have corresponding <code>getDefaultXXX</code> methods.
 * </p>
 * 
 * @author Bindul Bhowmik
 */
public class DerbyResourceConfig {
	
	// ---------------------------------------------------------------------------------- Database location and protocol
	
	/**
	 * The database subsubprotocol for the JDBC URL
	 */
	private JdbcDerbySubSubProtocol subSubProtocol;
	
	/**
	 * This is a multi purpose field; it is used as the end of the JDBC URL.
	 * <ul>
	 * <li>For a memory database, it is the database name;</li>
	 * <li>For a directory, it is the absolute / relative directory path;</li>
	 * <li>For a jar database, it is the path of the database inside the jar file;</li>
	 * </ul>
	 */
	private String databasePath;

	/**
	 * Right now only used for the :jar: protocol for the jar file.
	 */
	private String jarDatabaseJarFile;
	
	/**
	 * Skip the <code>create=true</code> attribute in the connection URL
	 */
	private boolean directoryDatabaseSkipCreate = false;
	
	// --------------------------------------------- Parameters to control restoring a DB or creating one from a backup.
	
	/**
	 * How are we restoring
	 */
	private DbCreateFromRestroreMode dbCreateFromRestoreMode;
	
	/**
	 * Where are we restoring from
	 */
	private File dbCreateFromRestoreFrom;
	
	/**
	 * Where are the archive logs
	 */
	private File dbRecoveryLogDevice;
	
	// Logging controls
	
	// TODO Complete configuring logging
	private ErrorLoggingMode errorLoggingMode;
	
	/**
	 * Post init scripts
	 */
	private List<String> postInitScripts;
	
	/**
	 * Sets up a default config that can be used as is to start a database. See the appropriate
	 * <code>getDefaultXXX</code> methods to see the default values.
	 * 
	 * @return A config setup with defaults.
	 */
	public static DerbyResourceConfig buildDefault () {
		final DerbyResourceConfig config = new DerbyResourceConfig();
		config.useInMemoryDatabase();
		config.errorLoggingMode = getDefaultErrorLoggingMode();
		// TODO Complete setting defaults
		return config;
	}
	
	// -----------------------------------------------------------------------------------------------------------------
	// START: Database location and protocol
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	/**
	 * Returns the Jar database jar file path. Right now only used for the <code>:jar:</code> protocol for the jar file.
	 * Returns the file path of the jar file with the read only database.
	 * 
	 * @return The jar file path
	 * @see #useJarSubSubProtocol(String, String)
	 * @see <a href="http://db.apache.org/derby/docs/10.12/devguide/cdevdeploy11201.html">Accessing a read-only database
	 * in a zip/jar file</a>
	 */
	public String getJarDatabaseJarFile () {
		return jarDatabaseJarFile;
	}
	
	/**
	 * Returns the name/path of the database to use. This is a multi purpose field; it is used as the end of the JDBC
	 * URL.
	 * <ul>
	 * <li>For a memory database, it is the database name;</li>
	 * <li>For a directory, it is the absolute / relative directory path;</li>
	 * <li>For a jar database, it is the path of the database inside the jar file;</li>
	 * </ul>
	 * 
	 * <p>Consult the documentation for Derby Sub Sub Protocols on database name formats and use.
	 * 
	 * @return The database name.
	 * @see #getDefaultDatabasePathName()
	 * @see <a href="http://db.apache.org/derby/docs/10.11/ref/rrefjdbc37352.html">Syntax of db connection URLs</a>
	 */
	public String getDatabasePath () {
		return databasePath;
	}
	
	/**
	 * For a database using the {@link JdbcDerbySubSubProtocol#Directory} subsubprotocol, skip the
	 * <code>create=true</code> attribute.
	 * 
	 * @return the directoryDatabaseSkipCreate
	 */
	public boolean isDirectoryDatabaseSkipCreate () {
		return directoryDatabaseSkipCreate;
	}

	/**
	 * Returns the default database name value, which is a UUID string.
	 * @return The default database name.
	 */
	public static String getDefaultDatabasePathName () {
		return UUID.randomUUID().toString();
	}
	
	/**
	 * Will have the database start up as an in-memory database with a database name generated using
	 * {@link #getDefaultDatabasePathName()}.
	 * 
	 * @return This instance
	 */
	public DerbyResourceConfig useInMemoryDatabase () {
		resetSubSubProtocolSpecificValues();

		this.subSubProtocol = JdbcDerbySubSubProtocol.Memory;
		this.databasePath = getDefaultDatabasePathName();
		return this;
	}
	
	/**
	 * Will have the database start up as an in-memory database with the specified database name.
	 * 
	 * @param databaseName The name of the database
	 * @return This instance
	 */
	public DerbyResourceConfig useInMemoryDatabase (final String databaseName) {
		ArgumentCheck.notNullOrEmpty(databaseName, "database name");
		resetSubSubProtocolSpecificValues();

		this.subSubProtocol = JdbcDerbySubSubProtocol.Memory;
		this.databasePath = databaseName;
		return this;
	}

	/**
	 * Use the <code>:directory:</code> Derby sub sub protocol. The database will be created in a directory named
	 * with the {@link #getDefaultDatabasePathName()} as the directory name.
	 * 
	 * @return This instance
	 */
	public DerbyResourceConfig useDatabaseInDirectory () {
		resetSubSubProtocolSpecificValues();

		this.subSubProtocol = JdbcDerbySubSubProtocol.Directory;
		this.databasePath = getDefaultDatabasePathName();
		return this;
	}
	
	/**
	 * Use the <code>:directory:</code> Derby sub sub protocol, with the database in the specified
	 * <code>directorpyDbPath</code>. The path is either relative or absolute as interpreted by the Derby engine.
	 * 
	 * @param directorpyDbPath The relative or absolute path where the database is created.
	 * @return This instance
	 */
	public DerbyResourceConfig useDatabaseInDirectory (final String directorpyDbPath) {
		return useDatabaseInDirectory(directorpyDbPath, false);
	}

	/**
	 * Use the <code>:directory:</code> Derby sub sub protocol, with the database in the specified
	 * <code>directorpyDbPath</code>. The path is either relative or absolute as interpreted by the Derby engine. If the
	 * <code>skipCreateAttribute</code> attribute is set to <code>true</code>, the Database will be initialized without
	 * the <code>create=true</code> attribute.
	 * 
	 * @param directorpyDbPath The relative or absolute path where the database is created.
	 * @param skipCreateAttribute Skip the <code>create=true</code> attribute in the connection URL
	 * @return This instance
	 */
	public DerbyResourceConfig useDatabaseInDirectory (final String directorpyDbPath,
			final boolean skipCreateAttribute) {

		ArgumentCheck.notNullOrEmpty(directorpyDbPath, "database path");
		resetSubSubProtocolSpecificValues();

		this.subSubProtocol = JdbcDerbySubSubProtocol.Directory;
		this.databasePath = directorpyDbPath;
		this.directoryDatabaseSkipCreate = skipCreateAttribute;
		return this;
	}

	/**
	 * Use the <code>:jar:</code> Derby sub sub protocol. The jar file containing the read only database is a relative
	 * or absolute path in <code>jarFilePath</code> and the database path in the <code>dbPath</code> parameter. The
	 * <code>jarFilePath</code> is either relative or absolute as interpreted by the Derby engine.
	 * 
	 * <p>For more format information see <a href="http://db.apache.org/derby/docs/10.12/devguide/cdevdeploy11201.html">
	 * Accessing a read-only database in a zip/jar file</a> and <a
	 * href="http://db.apache.org/derby/docs/10.12/devguide/cdevdvlp24155.html">Accessing databases from a jar or zip
	 * file</a> in the Derby Developer's guide.
	 * 
	 * @param jarFilePath The relative or absolute path where the jar file with the read-only database.
	 * @param dbPath The path of the database inside the jar file.
	 * @return This instance
	 */
	public DerbyResourceConfig useJarSubSubProtocol (final String jarFilePath, final String dbPath) {
		ArgumentCheck.notNullOrEmpty(jarFilePath, "Jar database path");
		ArgumentCheck.notNullOrEmpty(dbPath, "database path");
		resetSubSubProtocolSpecificValues();

		this.subSubProtocol = JdbcDerbySubSubProtocol.Jar;
		this.jarDatabaseJarFile = jarFilePath;
		this.databasePath = dbPath;
		return this;
	}
	
	/**
	 * Use the <code>:classpath:</code> Derby sub sub protocol. This allows access to a read only database in the
	 * classpath (see Derby documentation on <a href="http://db.apache.org/derby/docs/10.12/devguide/cdevdvlp91854.html"
	 * >Accessing databases from the classpath</a>). The database can be either in a Jar file or directly in the
	 * classpath.
	 * 
	 * <p>The <code>dbPath</code> parameter designates the path to the database in the classpath. All databaseNames must
	 * begin with at least a slash, because you specify them "relative" to the classpath directory or archive. See also
	 * <a href="http://db.apache.org/derby/docs/10.12/ref/rrefjdbc37352.html">Syntax of database connection URLs for
	 * applications with embedded databases</a> and <a
	 * href="http://db.apache.org/derby/docs/10.12/devguide/tdevdeploy39856.html">Accessing databases within a jar file
	 * using the classpath</a>.
	 * 
	 * @param dbPath The path of the database in the classpath
	 * @return This instance
	 */
	public DerbyResourceConfig useClasspathSubSubProtocol (final String dbPath) {
		ArgumentCheck.notNullOrEmpty(dbPath, "database path");
		resetSubSubProtocolSpecificValues();

		this.subSubProtocol = JdbcDerbySubSubProtocol.Classpath;
		this.databasePath = dbPath;
		return this;
	}
	
	/**
	 * The JDBC sub-sub protocol to use for the embedded database.
	 * 
	 * @return A valid Derby sub-sub protocol
	 */
	public JdbcDerbySubSubProtocol getSubSubProtocol () {
		return this.subSubProtocol;
	}
	
	/**
	 * The default sub-sub protocol value.
	 * @return {@link JdbcDerbySubSubProtocol#Memory}
	 */
	public static JdbcDerbySubSubProtocol getDefaultSubSubProtocol () {
		return JdbcDerbySubSubProtocol.Memory;
	}

	private void resetSubSubProtocolSpecificValues () {
		this.databasePath = null;
		this.jarDatabaseJarFile = null;
		this.directoryDatabaseSkipCreate = false;
	}
	
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// END: Database location and protocol
	// -----------------------------------------------------------------------------------------------------------------

	// -----------------------------------------------------------------------------------------------------------------
	// START: Parameters to control restoring a DB or creating one from a backup.
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	/**
	 * Controls if the database should be created or restored from a backup. A <code>null</code> value returned from
	 * this method means no restore. If this is not null, the location of the backup database is returned from
	 * {@link #getDbCreateFromRestoreFrom()}; and for roll-forward recovery, the log device is at
	 * {@link #getDbRecoveryLogDevice()}.
	 * 
	 * <p>See the <a href="http://db.apache.org/derby/docs/10.12/adminguide/cadminhubbkup98797.html">Backing up and
	 * restoring a database</a> section of the Derby Administration guide for more information on means to backup and
	 * restore a database.
	 * 
	 * @see EmbeddedDerbyResource#backupLiveDatabase(File, boolean, boolean, boolean)
	 * 
	 * @return The mode to create / restore a database from a backup.
	 */
	public DbCreateFromRestroreMode getDbCreateFromRestoreMode () {
		return dbCreateFromRestoreMode;
	}

	/**
	 * The location of the database backup to use. This is usually a full backup of the database.
	 * 
	 * @see #getDbCreateFromRestoreMode()
	 * 
	 * @return The database backup location.
	 */
	public File getDbCreateFromRestoreFrom () {
		return dbCreateFromRestoreFrom;
	}

	/**
	 * Archive log location for roll-forward database recovery. See
	 * <a href="http://db.apache.org/derby/docs/10.12/adminguide/cadminrollforward.html">Roll-forward recovery</a> in
	 * the Derby administration guide to learn more about archive logging.
	 * 
	 * @return The Database directory from which archive logs are available.
	 */
	public File getDbRecoveryLogDevice () {
		return dbRecoveryLogDevice;
	}
	
	/**
	 * Restore a database from a backup location. If a database with the same name exists, the system will delete the
	 * database, copy it from the backup and restart it.
	 * 
	 * @param dbBackupDir The backup location.
	 * @return This instance.
	 */
	public DerbyResourceConfig restoreDatabaseFrom (final File dbBackupDir) {
		ArgumentCheck.notNull(dbBackupDir, "Database backup directory");
		resetDbCreateRestoreConfigs ();

		this.dbCreateFromRestoreMode = DbCreateFromRestroreMode.RestoreFrom;
		this.dbCreateFromRestoreFrom = dbBackupDir;
		return this;
	}
	
	/**
	 * Create a new database from a backup copy. If there is already a database with the same name in derby.system.home,
	 * an error will occur and the existing database will be left intact.
	 * 
	 * @param dbBackupDir The location of the database backup.
	 * @return This instance.
	 */
	public DerbyResourceConfig createDatabaseFrom (final File dbBackupDir) {
		ArgumentCheck.notNull(dbBackupDir, "Database backup directory");
		resetDbCreateRestoreConfigs ();

		this.dbCreateFromRestoreMode = DbCreateFromRestroreMode.CreateFrom;
		this.dbCreateFromRestoreFrom = dbBackupDir;
		return this;
	}
	
	/**
	 * Restore a database with roll forward recovery, optionally with archive logs. To see the steps involved in
	 * performing a roll forward recovery, see
	 * <a href="http://db.apache.org/derby/docs/10.12/adminguide/cadminrollforward.html">Roll Forward Recovery</a> in
	 * the Derby Administrative Guide.
	 * 
	 * @param dbBackupDir The database backup location.
	 * @param recoveryLogDevice The archive log location.
	 * @return This instance.
	 */
	public DerbyResourceConfig recoverDatabaseFrom (final File dbBackupDir, final File recoveryLogDevice) {
		ArgumentCheck.notNull(dbBackupDir, "Database backup directory");
		ArgumentCheck.notNull(recoveryLogDevice, "Recovery log device");
		resetDbCreateRestoreConfigs ();

		this.dbCreateFromRestoreMode = DbCreateFromRestroreMode.RollForwardRecoveryFrom;
		this.dbCreateFromRestoreFrom = dbBackupDir;
		this.dbRecoveryLogDevice = recoveryLogDevice;
		return this;
	}
	
	private void resetDbCreateRestoreConfigs () {
		this.dbCreateFromRestoreMode = null;
		this.dbCreateFromRestoreFrom = null;
		this.dbRecoveryLogDevice = null;
	}

	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// END: Parameters to control restoring a DB or creating one from a backup.
	// -----------------------------------------------------------------------------------------------------------------
	
	// -----------------------------------------------------------------------------------------------------------------
	// START: Logging controls
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	/**
	 * Sets the {@link #getErrorLoggingMode()} value to {@link ErrorLoggingMode#Null}; and clears other logging
	 * properties.
	 * 
	 * @return This instance
	 */
	public DerbyResourceConfig useDevNullErrorLogging () {
		this.errorLoggingMode = ErrorLoggingMode.Null;
		//TODO clean out other values?
		return this;
	}
	
	/**
	 * Sets the {@link #getErrorLoggingMode()} value to {@link ErrorLoggingMode#Default}; and clears other logging
	 * properties.
	 * 
	 * @return This instance
	 */
	public DerbyResourceConfig useDefaultErrorLogging () {
		this.errorLoggingMode = ErrorLoggingMode.Default;
		//TODO clean out other values?
		return this;
	}
	
	/**
	 * The configured error logging mode.
	 * @return the configured error logging mode
	 */
	public ErrorLoggingMode getErrorLoggingMode () {
		return errorLoggingMode;
	}
	
	/**
	 * The default logging setup.
	 * @return {@link ErrorLoggingMode#Default}
	 */
	public static ErrorLoggingMode getDefaultErrorLoggingMode () {
		return ErrorLoggingMode.Default;
	}
	
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// END: Logging controls
	// -----------------------------------------------------------------------------------------------------------------
	
	// -----------------------------------------------------------------------------------------------------------------
	// START: Post init scripts
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	/**
	 * Gets the configured post init scripts in the config; or an empty list.
	 * @return Post init scripts to execute
	 */
	public List<String> getPostInitScripts () {
		if (null == postInitScripts) {
			return Collections.emptyList();
		}
		return postInitScripts;
	}
	
	/**
	 * Adds a post init script to the config.
	 * 
	 * <p>A post init script should refer to a file containing SQL DDL or DML statements that will be executed
	 * against the new derby instance. Script locations can be on the classpath or file system or on a HTTP(s)
	 * location, for format, see {@linkplain org.deventropy.shared.utils.UrlResourceUtil}.
	 * 
	 * @param postInitScript A post init script to add
	 * @return this object
	 */
	public DerbyResourceConfig addPostInitScript (final String postInitScript) {
		ArgumentCheck.notNullOrEmpty(postInitScript, "Post Init Script");
		if (null == postInitScripts) {
			postInitScripts = new ArrayList<>();
		}
		postInitScripts.add(postInitScript);
		return this;
	}
	
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// END: Post init scripts
	// -----------------------------------------------------------------------------------------------------------------
}