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. |