|
From: <fu...@us...> - 2010-01-26 17:46:43
|
Revision: 1026
http://cishell.svn.sourceforge.net/cishell/?rev=1026&view=rev
Author: fugu13
Date: 2010-01-26 17:46:36 +0000 (Tue, 26 Jan 2010)
Log Message:
-----------
Database Copy stuff. Reviewed by Patrick.
Modified Paths:
--------------
trunk/core/org.cishell.reference.service.database/src/org/cishell/reference/service/database/DerbyDatabaseService.java
trunk/core/org.cishell.reference.service.database/src/org/cishell/reference/service/database/InternalDerbyDatabase.java
trunk/core/org.cishell.service.database/src/org/cishell/service/database/DatabaseService.java
Modified: trunk/core/org.cishell.reference.service.database/src/org/cishell/reference/service/database/DerbyDatabaseService.java
===================================================================
--- trunk/core/org.cishell.reference.service.database/src/org/cishell/reference/service/database/DerbyDatabaseService.java 2010-01-26 16:51:30 UTC (rev 1025)
+++ trunk/core/org.cishell.reference.service.database/src/org/cishell/reference/service/database/DerbyDatabaseService.java 2010-01-26 17:46:36 UTC (rev 1026)
@@ -1,13 +1,13 @@
package org.cishell.reference.service.database;
+import java.io.File;
+import java.sql.CallableStatement;
import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
import java.sql.SQLException;
-import java.sql.Statement;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
+import java.util.UUID;
import javax.sql.DataSource;
@@ -20,179 +20,269 @@
import org.apache.commons.pool.impl.GenericObjectPool;
import org.cishell.reference.service.database.utility.DatabaseCleaner;
import org.cishell.service.database.Database;
+import org.cishell.service.database.DatabaseCopyException;
import org.cishell.service.database.DatabaseCreationException;
import org.cishell.service.database.DatabaseService;
+import org.cishell.utilities.DatabaseUtilities;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
+//TODO: rework exception handling everywhere to be failsafe.
public class DerbyDatabaseService implements DatabaseService, BundleActivator {
-
+
public static final String DERBY_DRIVER_NAME =
"org.apache.derby.jdbc.EmbeddedDriver";
public static final String DERBY_PROTOCOL = "jdbc:derby:";
-
+
public static final String DEFAULT_CREATE_CONNECTION_STRING = ";create=true";
public static final String DEFAULT_SHUTDOWN_CONNECTION_STRING =";shutdown=true";
-
+
//where the database exists on the filesystem (relative to the application root directory)
private static final String DATABASE_DIRECTORY = "database/";
-
+
//hold on to our service registration so we can unregister when this plugin stops.
private ServiceRegistration databaseServiceRegistration;
-
+
/*
* internal databases are created when requested by outside services,
* and are cleaned and shut down when this service is stopped.
*/
private List<InternalDerbyDatabase> internalDatabases = new ArrayList<InternalDerbyDatabase>();
-
+
public final void start(BundleContext context) throws Exception {
-
+
/*
* Tell Derby to look for an existing database or create a new database
* in the default directory (within our application's directory)
*/
System.setProperty("derby.system.home", DATABASE_DIRECTORY);
-
+
//Allow the database service to be found by other services/plugins
databaseServiceRegistration = context.registerService(
DatabaseService.class.getName(), this, new Hashtable());
}
-
+
public final void stop(BundleContext context) {
- //disallow the database service to be found by other services/plugins
- this.databaseServiceRegistration.unregister();
-
- //Clean out the internal databases and shut them down.
- try {
- for (InternalDerbyDatabase internalDatabase : internalDatabases) {
- Connection internalDatabaseConnection = internalDatabase.getConnection();
- //DatabaseCleaner.cleanDatabase(internalDatabaseConnection, false);
- internalDatabase.shutdown();
- }
- } catch (Exception e) {
- String message =
- "An unexpected exception occurred while shutting down the internal database."
- + "Aborting database shutdown process."
- + "Database may not be left in a valid state, "
- + "but we will try to make its state valid on next startup.";
- throw new RuntimeException(message, e);
+ //disallow the database service to be found by other services/plugins
+ this.databaseServiceRegistration.unregister();
+
+ //Clean out the internal databases and shut them down.
+ try {
+ for (InternalDerbyDatabase internalDatabase : internalDatabases) {
+ Connection internalDatabaseConnection = internalDatabase.getConnection();
+ //DatabaseCleaner.cleanDatabase(internalDatabaseConnection, false);
+ internalDatabase.shutdown();
}
+ } catch (Exception e) {
+ String message =
+ "An unexpected exception occurred while shutting down the internal database."
+ + "Aborting database shutdown process."
+ + "Database may not be left in a valid state, "
+ + "but we will try to make its state valid on next startup.";
+ throw new RuntimeException(message, e);
+ }
}
-
+
private static final String INTERNAL_DB_NAME_PREFIX = "cishell_database";
private static int id = 0;
-
- public synchronized Database createNewDatabase() throws DatabaseCreationException {
- try {
+
+ public Database createNewDatabase() throws DatabaseCreationException {
//connect to and create a 'new' database
- String databaseName = INTERNAL_DB_NAME_PREFIX + id;
- InternalDerbyDatabase db =
- new InternalDerbyDatabase(createNewInternalDataSource(databaseName));
-
-
- //if this database existed on disk from a previous session, clean it to be like new
- DatabaseCleaner.cleanDatabase(db.getConnection(), false);
-
- //keep track of our new database for this CIShell session
- internalDatabases.add(db);
-
- id++;
-
- return db;
- } catch (Exception e) {
- throw new DatabaseCreationException(e);
- }
+ String databaseName = nextDatabaseIdentifier();
+ DataSource internalDataSource = createNewInternalDataSource(databaseName);
+ //TODO: find a way to get the darn database name that isn't awful!
+ InternalDerbyDatabase internalDatabase = createNewInternalDatabase(internalDataSource);
+ internalDatabase.setName(databaseName);
+ return internalDatabase;
}
-
+
+
+
public Database connectToExistingDatabase(String driver, String url)
- throws DatabaseCreationException {
+ throws DatabaseCreationException {
return connectToExistingDatabase(driver, url, null, null);
}
-
+
public Database connectToExistingDatabase(
String driver, String url, String username, String password)
- throws DatabaseCreationException {
+ throws DatabaseCreationException {
DataSource dataSource =
createNewDataSource(driver, url, username, password);
//TODO: See if we can get the default schema as a property somehow.
Database db = new ExternalDatabase(dataSource, "APP");
return db;
}
+
+
+
+ public Database copyDatabase(Database originalDatabase) throws DatabaseCopyException {
+ /* Connection originalConnection = null;
+ Connection newConnection = null;
+ try {
+ Database newDatabase = createNewDatabase();
+ originalConnection = originalDatabase.getConnection();
+ try {
+ newConnection = newDatabase.getConnection();
+ DatabaseTable[] tables = DatabaseTable.availableTables(originalConnection);
+ for(DatabaseTable table : tables) {
+ table.duplicateTable(originalConnection, newConnection);
+ }
+ for(DatabaseTable table : tables) {
+ table.transferPrimaryKey(originalConnection, newConnection);
+ table.pointForeignKeys(originalConnection, newConnection);
+ }
+
+
+ } catch(SQLException e) {
+ throw new DatabaseCopyException("There was a problem creating the new database: " + e.getMessage(), e);
+ }
+ } catch (DatabaseCreationException e) {
+ throw new DatabaseCopyException("Unable to create a new database to copy into: " + e.getMessage(), e);
+ } catch (SQLException e) {
+ throw new DatabaseCopyException("Unable to connect to the database being copied.", e);
+ } finally {
+ DatabaseUtilities.closeConnectionQuietly(originalConnection);
+ DatabaseUtilities.closeConnectionQuietly(newConnection);
+ }
+
+ //TODO: copy views. Wait until we have the darn things.
+ /*
+ * TODO: make copy table work
+ * On the subject of copying tables . . . looks like that needs to be provided. Rough plan of attack:
+ * Make new database. For every table in the original (make sure system tables are excluded), make an identical table in the new one.
+ * For every table, copy all values into the new database.
+ * Put all constraints in place on the new database (especially: primary keys, foreign keys)
+ */
+
+ //Make a backup, then make a new database derived from the backup.
+ if(originalDatabase instanceof InternalDerbyDatabase) {
+ InternalDerbyDatabase database = (InternalDerbyDatabase) originalDatabase;
+ String originalName = database.getName();
+ Connection connection = null;
+ try {
+ connection = database.getConnection();
+ String backupLocation = createDatabaseBackup(connection,
+ originalName);
+
+ InternalDerbyDatabase internalDatabase = createInternalDatabaseFromBackup(backupLocation);
+ return internalDatabase;
+
+
+ } catch (SQLException e) {
+ throw new DatabaseCopyException("A problem occurred while attempting to copy the database.", e);
+ } catch (DatabaseCreationException e) {
+ throw new DatabaseCopyException("A problem occurred while attempting to copy the database.", e);
+ } finally {
+ DatabaseUtilities.closeConnectionQuietly(connection);
+ }
+
+
+
+ } else {
+ throw new DatabaseCopyException("Unable to copy external databases!");
+ }
+ }
+
+ private InternalDerbyDatabase createInternalDatabaseFromBackup(
+ String backupLocation) throws DatabaseCreationException {
+ String newName = nextDatabaseIdentifier();
+ String newDatabaseConnectionURL = DERBY_PROTOCOL + newName + ";restoreFrom=" + backupLocation;
+ DataSource derivedDataSource = createNewDataSource(DERBY_DRIVER_NAME,
+ newDatabaseConnectionURL, null, null);
+ InternalDerbyDatabase internalDatabase = createNewInternalDatabase(derivedDataSource, false);
+ internalDatabase.setName(newName);
+ return internalDatabase;
+ }
+
+ private String createDatabaseBackup(Connection connection,
+ String originalName) throws SQLException {
+ CallableStatement backupStatement = connection.prepareCall("CALL SYSCS_UTIL.SYSCS_BACKUP_DATABASE(?)");
+ String tempDir = System.getProperty("java.io.tmpdir");
+ backupStatement.setString(1, tempDir);
+ backupStatement.execute();
+ String backupLocation = new File(new File(tempDir), originalName).getAbsolutePath();
+ return backupLocation;
+ }
+
//***---UTILITIES---***
-
+
private DataSource createNewDataSource(
String driver, String url, String username, String password)
- throws DatabaseCreationException {
+ throws DatabaseCreationException {
try {
- //Load the database driver
- Class.forName(driver);
-
- //create a new data source based on the database connection info provided.
- ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(
- url, username, password);
- GenericObjectPool connectionPool = new GenericObjectPool();
+ //Load the database driver
+ Class.forName(driver);
+
+ //create a new data source based on the database connection info provided.
+ ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(
+ url, username, password);
+ GenericObjectPool connectionPool = new GenericObjectPool();
KeyedObjectPoolFactory stmtPool = new GenericKeyedObjectPoolFactory(null);
new PoolableConnectionFactory(
connectionFactory, connectionPool, stmtPool, null, false, true);
DataSource dataSource = new PoolingDataSource(connectionPool);
-
+
//test the connection (this will throw an exception if the connection is faulty)
dataSource.getConnection();
-
+
//return that data source.
- return dataSource;
- } catch (ClassNotFoundException e) {
- throw new DatabaseCreationException(
- "Database driver '" + driver + "' could not be found", e);
- } catch (SQLException e) {
- throw new DatabaseCreationException(e.getMessage(), e);
- }
+ return dataSource;
+ } catch (ClassNotFoundException e) {
+ throw new DatabaseCreationException(
+ "Database driver '" + driver + "' could not be found", e);
+ } catch (SQLException e) {
+ throw new DatabaseCreationException(e.getMessage(), e);
+ }
}
-
+
private DataSource createNewInternalDataSource(String dbName)
- throws DatabaseCreationException {
+ throws DatabaseCreationException {
String newDatabaseConnectionURL = DERBY_PROTOCOL
- + dbName
- + DEFAULT_CREATE_CONNECTION_STRING;
+ + dbName
+ + DEFAULT_CREATE_CONNECTION_STRING;
return createNewDataSource(DERBY_DRIVER_NAME, newDatabaseConnectionURL, null, null);
}
- private static final int SCHEMA_NAME_INDEX = 2;
- private static final int TABLE_NAME_INDEX = 3;
- private static final String NONSYSTEM_SCHEMA_NAME = "APP";
-// //(removes all non-system tables from the provided databases)
-// private void removeAllNonSystemDatabaseTables(Connection dbConnection) throws SQLException {
-// DatabaseMetaData dbMetadata = dbConnection.getMetaData();
-// ResultSet allTableNames = dbMetadata.getTables(null, null, null, null);
-//
-// Statement removeTables = dbConnection.createStatement();
-//
-// while (allTableNames.next()) {
-// if (!hasSystemSchema(allTableNames.getString(SCHEMA_NAME_INDEX))) {
-// String dropTableQuery = formDropTableQuery(allTableNames.getString(TABLE_NAME_INDEX));
-// removeTables.addBatch(dropTableQuery);
-// }
-// }
-//
-// removeTables.executeBatch();
-// }
-
- private boolean hasSystemSchema(String tableSchemaName) {
- return tableSchemaName.indexOf(NONSYSTEM_SCHEMA_NAME) == -1;
+ private InternalDerbyDatabase createNewInternalDatabase(DataSource internalDataSource) throws DatabaseCreationException {
+ return createNewInternalDatabase(internalDataSource, true);
}
-
- private String formDropTableQuery(String tableName) {
- String removeTableSQL =
- "DROP TABLE "
- + NONSYSTEM_SCHEMA_NAME + "." + tableName;
- return removeTableSQL;
+
+ private InternalDerbyDatabase createNewInternalDatabase(DataSource internalDataSource, boolean clean)
+ throws DatabaseCreationException {
+ InternalDerbyDatabase db =
+ new InternalDerbyDatabase(internalDataSource);
+ Connection cleaningConnection = null;
+ try {
+ //if this database existed on disk from a previous session, clean it to be like new
+ if(clean) {
+ cleaningConnection = db.getConnection();
+ DatabaseCleaner.cleanDatabase(cleaningConnection, false);
+ }
+
+ //keep track of our new database for this CIShell session
+ internalDatabases.add(db);
+
+
+ return db;
+ } catch (Exception e) {
+ DatabaseUtilities.closeConnectionQuietly(cleaningConnection);
+ throw new DatabaseCreationException(e);
+ }
}
+ //only thing that needs to be synchronized; all other ops are non-conflicting once they have differing names
+ private synchronized String nextDatabaseIdentifier() {
+ //Random to deal with the multiple running CIShell instance problem. Note: this means databases will build up. This is not a big deal, as even a "large" database will only be a few megabytes.
+ String randomPart = UUID.randomUUID().toString();
+ String identifier = INTERNAL_DB_NAME_PREFIX + randomPart + "_" + id;
+ id++;
+ return identifier;
+ }
+
}
Modified: trunk/core/org.cishell.reference.service.database/src/org/cishell/reference/service/database/InternalDerbyDatabase.java
===================================================================
--- trunk/core/org.cishell.reference.service.database/src/org/cishell/reference/service/database/InternalDerbyDatabase.java 2010-01-26 16:51:30 UTC (rev 1025)
+++ trunk/core/org.cishell.reference.service.database/src/org/cishell/reference/service/database/InternalDerbyDatabase.java 2010-01-26 17:46:36 UTC (rev 1026)
@@ -11,7 +11,8 @@
public class InternalDerbyDatabase implements Database {
private DataSource dataSource;
-
+ private String databaseName;
+ // TODO: Should this be public?
public InternalDerbyDatabase(DataSource dataSource) {
this.dataSource = dataSource;
}
@@ -33,4 +34,13 @@
public String getApplicationSchemaName() {
return "APP";
}
+
+
+ protected void setName(String databaseName) {
+ this.databaseName = databaseName;
+ }
+
+ protected String getName() {
+ return databaseName;
+ }
}
Modified: trunk/core/org.cishell.service.database/src/org/cishell/service/database/DatabaseService.java
===================================================================
--- trunk/core/org.cishell.service.database/src/org/cishell/service/database/DatabaseService.java 2010-01-26 16:51:30 UTC (rev 1025)
+++ trunk/core/org.cishell.service.database/src/org/cishell/service/database/DatabaseService.java 2010-01-26 17:46:36 UTC (rev 1026)
@@ -7,4 +7,5 @@
public Database connectToExistingDatabase(
String driver, String url, String username, String password)
throws DatabaseCreationException;
+ public Database copyDatabase(Database originalDatabase) throws DatabaseCopyException;
}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|