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