/********************************************************************************
* *
* (c) Copyright 2010 Verizon Communications USA and The Open University UK *
* *
* This software is freely distributed in accordance with *
* the GNU Lesser General Public (LGPL) license, version 3 or later *
* as published by the Free Software Foundation. *
* For details see LGPL: http://www.fsf.org/licensing/licenses/lgpl.html *
* and GPL: http://www.fsf.org/licensing/licenses/gpl-3.0.html *
* *
* This software is provided by the copyright holders and contributors "as is" *
* and any express or implied warranties, including, but not limited to, the *
* implied warranties of merchantability and fitness for a particular purpose *
* are disclaimed. In no event shall the copyright owner or contributors be *
* liable for any direct, indirect, incidental, special, exemplary, or *
* consequential damages (including, but not limited to, procurement of *
* substitute goods or services; loss of use, data, or profits; or business *
* interruption) however caused and on any theory of liability, whether in *
* contract, strict liability, or tort (including negligence or otherwise) *
* arising in any way out of the use of this software, even if advised of the *
* possibility of such damage. *
* *
********************************************************************************/
package com.compendium.core.db.management;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ConcurrentModificationException;
import java.util.Enumeration;
import java.util.Vector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.compendium.core.CoreUtilities;
import com.compendium.core.ICoreConstants;
import com.compendium.core.datamodel.Model;
/**
* This class restores the data in a given sql file into the named database.
* It allows external classes to register DBProgressListeners and fires appropriate progress information to them.
* This facilitates the display of progress information in a user interface, if desired.
*
* @author Michelle Bachler
*/
public class DBRestoreDatabase implements DBConstants, DBProgressListener, DBConstantsMySQL, DBConstantsDerby {
/**
* class's own logger
*/
final Logger log = LoggerFactory.getLogger(getClass());
/** A Vector of registerd progress listeners, to recieve progress updates*/
protected Vector progressListeners;
private Model model = null;
/** The connection to use to load the data.*/
private Connection connection;
/** A count for the progress events to use*/
private int nCount = 5;
/** The increment for the progress counters to use*/
private int increment = 1;
/**
* A reference to the DBAdministrationManager to use to register the newly created database
* when restoring a file to a new database
*/
private DBAdminDatabase adminDatabase = null;
/** The name to use whn accessing the database */
private String databasename = ICoreConstants.sDEFAULT_DATABASE_USER;
/** The password to use when accessing the database */
private String databasepassword = ICoreConstants.sDEFAULT_DATABASE_PASSWORD;
/** The password to use when accessing the database */
private String databaseip = ICoreConstants.sDEFAULT_DATABASE_ADDRESS;
/** The type of the databasse application to create an empty database for.*/
private int nDatabaseType = -1;
/**
* This constructor takes a name and password to use when accessing the database,
* and the IP address of the server machine.
*
* @param nDatabaseType, the type of the database being used (e.g, MySQL, Derby).
* @param admin, the DBAdminDatabase instance to use to register the new database created.
* @param sDatabaseName, the name to use when creating the connection to the database.
* @param sDatabasePassword, the password to use when connection to the database.
* @param sDatabaseIP, the IP address of the server machine. The default if 'localhost'.
*/
public DBRestoreDatabase(int nDatabaseType, DBAdminDatabase admin, String sDatabaseName, String sDatabasePassword, String sDatabaseIP) {
progressListeners = new Vector();
this.adminDatabase = admin;
this.databasename = sDatabaseName;
this.databasepassword = sDatabasePassword;
this.nDatabaseType = nDatabaseType;
if (sDatabaseIP != null && !sDatabaseIP.equals(""))
this.databaseip = sDatabaseIP;
}
/**
* Restore the data in the passed file to the database with the given name.
*
* @param String sName, the name of the database to restore to.
* @param File file, the file holding the sql statements to restore from.
* @param boolean fullRecreation, idicates whether to drop an recreate all tables - not currently used.
*/
public boolean restoreDatabase(String sName, File file, boolean fullRecreation) {
fireProgressCount(100);
fireProgressUpdate(increment, "Opening Connection..");
boolean dataRestored = false;
try {
connection = DBConnectionManager.getPlainConnection(nDatabaseType, sName, databasename, databasepassword, databaseip);
if (connection == null)
throw new DBDatabaseTypeException("Database type "+nDatabaseType+" not found");
deleteAllData(connection); // Purge all tables of existing data before reloading
if (dataRestored = loadData(connection, file)) {
checkReferencePaths(connection);
}
fireProgressComplete();
try {
connection.close();
}
catch(ConcurrentModificationException io) {
log.info("Exception closing connection for restore database:\n\n"+io.getMessage());
}
}
catch (Exception ex) {
log.error("Error...", ex);
fireProgressComplete();
return false;
}
return dataRestored;
}
/**
* Restore the data in the passed File, to a new database with given database name.
*
* @param String sFriendlyName, the name of the new database to create and restore to.
* @param File file, the file holding the sql statements to restore from.
* @param boolean fullRecreation, indicates whether to drop an recreate all tables - not currently used.
* @exception java.lang.ClassNotFoundException
* @exception java.sql.SQLException
* @exception DBDatabaseNameException, thrown if a database with the name given in the constructor already exists.
* @exception DBDatabaseTypeException, thrown if a database connection of the specific type cannot be created.
* @exception DBProjectListException, thrown if the list of projects could not be loaded from the database.
*/
public boolean restoreDatabaseAsNew(String sFriendlyName, File file, boolean fullRecreation) throws ClassNotFoundException, SQLException, DBDatabaseNameException, DBDatabaseTypeException, DBProjectListException {
String sToName = CoreUtilities.cleanDatabaseName(sFriendlyName);
sToName = sToName.toLowerCase();
boolean dataRestored = false;
// ARE WE TRYING TO LOAD DATA FROM PRE THE TABLE NAME CHANGES OF VERSION 1.3.3
// IF SO, DO NOT CREATE NEW EMPTY DATABASE FIRST
String version = checkVersion(file);
if (version.equals("")) {
return false;
}
else if (version.equals("1.3") || version.equals("1.3.1") || version.equals("1.3.2")) {
connection = DBConnectionManager.getCreationConnection(nDatabaseType, sToName, databasename, databasepassword, databaseip);
}
else {
DBEmptyDatabase empty = new DBEmptyDatabase(nDatabaseType, adminDatabase, databasename, databasepassword, databaseip);
empty.addProgressListener(this);
fireProgressUpdate(increment, "Creating new database..");
empty.createEmptyDatabase(sToName);
connection = DBConnectionManager.getPlainConnection(nDatabaseType, sToName, databasename, databasepassword, databaseip);
if (connection == null) {
throw new DBDatabaseTypeException("Database type "+nDatabaseType+" not found");
}
}
nCount = 5;
fireProgressCount(100);
dataRestored = loadData(connection, file);
if (dataRestored) {
adminDatabase.addNewDatabase(sFriendlyName, sToName);
checkReferencePaths(connection);
fireProgressComplete();
}
try {
connection.close();
}
catch(ConcurrentModificationException io) {
log.info("Exception closing connection for restore As New database:\n\n"+io.getMessage());
}
return dataRestored;
}
/**
* Check that all the reference and image path just loaded have the correct file separators for the current platform.
* Update the data where required.
*
* @param Connection con, the connection to the database to use to restore the data.
* @exception java.sql.SQLException
*/
private void checkReferencePaths(Connection con) throws SQLException {
if (con == null)
return;
String platform = System.getProperty("os.name");
String os = platform.toLowerCase();
boolean isWindows = false;
if (os.indexOf("windows") != -1) {
isWindows = true;
}
PreparedStatement pstmt = con.prepareStatement("SELECT NodeID, Source, ImageSource from ReferenceNode");
ResultSet rs = pstmt.executeQuery();
fireProgressUpdate(increment, "Checking reference paths correct..");
if (rs != null) {
String cleanSource = "";
String cleanImage = "";
while (rs.next()) {
String nodeid = rs.getString(1);
String source = rs.getString(2);
String image = rs.getString(3);
cleanSource = "";
cleanImage = "";
if (source != null && !source.equals("") && CoreUtilities.isFile(source)) {
cleanSource = CoreUtilities.cleanPath(source, isWindows);
if (!cleanSource.equals(source)) {
String statement = "UPDATE ReferenceNode SET Source=? WHERE NodeID='"+nodeid+"'";
PreparedStatement pstmt2 = con.prepareStatement(statement);
pstmt2.setString(1, cleanSource);
int nRowCount = pstmt2.executeUpdate();
pstmt2.close();
if (nRowCount == 0) {
log.info("Unable to update source = "+cleanSource);
}
}
}
if (image != null && !image.equals("")) {
cleanImage = CoreUtilities.cleanPath( image, isWindows );
if (!cleanImage.equals(image)) {
String statement = "UPDATE ReferenceNode SET ImageSource=? WHERE NodeID='"+nodeid+"'";
PreparedStatement pstmt3 = con.prepareStatement(statement);
pstmt3.setString(1, cleanImage);
int nRowCount = pstmt3.executeUpdate();
pstmt3.close();
if (nRowCount == 0) {
log.info("Unable to update image = "+cleanImage);
}
}
}
}
}
}
/**
* Load and restore the data in the given File, using the given Connection.
*
* @param Connection con, the connection to the database to use to restore the data.
* @param File file, the file object to get the sql statements from to restore the data.
* @return boolean, true if the restoration was successful, else false.
*/
private boolean loadData(Connection con, File file) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
}
catch (FileNotFoundException e) {
fireProgressAlert("The sql data file could not be found. Restoration cancelled.");
return false;
}
Vector statements = new Vector(51);
String version = "";
String line = "";
String header = "";
int nLines = 0;
// Make a first pass through the file, counting the number of lines in it
// and picking out the header and version for later use
try {
fireProgressUpdate(increment, "Scanning data..");
int ind = 0;
while (reader.ready()) {
line = reader.readLine();
nLines++;
if (line.startsWith(DBBackupDatabase.MYSQL_DATABASE_HEADER_CHECK)
|| line.startsWith(DBBackupDatabase.DERBY_DATABASE_HEADER_CHECK)) {
header = line;
// Compendium 1.4.2 and later only this was introduced
ind = line.indexOf(":");
if (ind != -1) {
version = line.substring(ind+1);
}
}
}
// Close and reopen the file (do it this way since you can't 'seek' or 'rewind' a BufferedReader
// Error checking not necessary since we've already successfully opened it once
reader.close();
reader = new BufferedReader(new FileReader(file));
if (nDatabaseType == ICoreConstants.DERBY_DATABASE && header.startsWith(DBBackupDatabase.MYSQL_DATABASE_HEADER_CHECK)) {
fireProgressAlert("This backup file is for a MySQL database project");
return false;
}
else if (nDatabaseType == ICoreConstants.MYSQL_DATABASE && header.startsWith(DBBackupDatabase.DERBY_DATABASE_HEADER_CHECK)) {
fireProgressAlert("This backup file is for a Derby database project");
return false;
}
fireProgressCount(nLines);
Statement stmt = con.createStatement();
int nRowCount = 0;
int Lines = 0;
while (reader.ready()) {
line = reader.readLine();
//Don't process the header line
if (line.startsWith(DBBackupDatabase.MYSQL_DATABASE_HEADER_CHECK)
|| line.startsWith(DBBackupDatabase.DERBY_DATABASE_HEADER_CHECK)) {
continue;
}
Lines++;
fireProgressUpdate(increment, "Adding database records...");
if (line != null) {
line = line.trim();
if (!line.equals("")) {
if (!line.startsWith("DROP") && !line.startsWith("CREATE")) { // Skip over troublesome statements
line = line.replace("\\n", "\n");
line = line.replace("\\\\n", "\n");
line = line.replace("\\r", "\r");
line = line.replace("\\\\r", "\r");
nRowCount = 0;
try {
nRowCount = stmt.executeUpdate(line);
}
catch(Exception ex) {
log.info("Problem with restoring = "+ex.getMessage());
log.error("Error...", ex);
}
}
}
}
}
reader.close();
/*if (version.equals("1.3.9")) { // == 1.4.2
JOptionPane.showMessageDialog(null, "This backup file is from a previous version\nof the Compendium database.\n\nIt cannot be used to restore from.", "Restoration error", JOptionPane.INFORMATION_MESSAGE);
return false;
}*/
stmt.close();
return true;
}
catch (IOException e) {
fireProgressAlert("There has been a problem loading the data:\n\n"+e.getMessage());
log.error("Error...", e);
return false;
}
catch (SQLException e) {
fireProgressAlert("There has been a problem loading the data:\n\n"+e.getMessage());
log.error("Error...", e);
return false;
}
}
private void deleteAllData(Connection con) throws SQLException {
if (nDatabaseType == ICoreConstants.DERBY_DATABASE) {
dropTables(con, DERBY_DROP_TABLES);
} else if (nDatabaseType == ICoreConstants.MYSQL_DATABASE) {
dropTables(con, MYSQL_DROP_TABLES);
}
}
/**
* Drop all the tables for the current database.
*
* @param Connection con, the connection to use to access the database.
* @param String[], the SQL strings to drop the tables with.
* @exception java.sql.SQLException
*/
private void dropTables(Connection con, String[] tables) throws SQLException {
PreparedStatement pstmt = null;
for (int i= 0; i < tables.length; i++) {
pstmt = con.prepareStatement(tables[i]);
pstmt.executeUpdate() ;
pstmt.close();
}
}
/**
* Check which version of the database schema the data to load is from.
*
* @param File file, the file object to get the sql statements from to restore the data.
* @return boolean, true if the restoration was successful, else false.
*/
private String checkVersion(File file) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
}
catch (FileNotFoundException e) {
fireProgressAlert("The sql data file could not be found. Restoration cancelled.");
return new String("");
}
String version = "";
String line = "";
try {
fireProgressUpdate(increment, "Checking data version..");
while (reader.ready()) {
line = reader.readLine();
if (line != null) {
line = line.trim();
if (!line.equals("")) {
if (line.startsWith("INSERT INTO System")) {
int cut = line.indexOf("VALUES ('version'");
if (cut != -1) {
int bracket = line.indexOf(")", cut);
version = line.substring(cut+19, bracket-1);
return version;
}
}
}
}
}
reader.close();
return new String("");
}
catch (IOException e) {
fireProgressAlert("There has been a problem loading the data:\n\n"+e.getMessage());
log.error("Error...", e);
return new String("");
}
}
// IMPLEMENT PROGRESS LISTENER
/**
* Set the amount of progress items being counted.
*
* @param int nCount, the amount of progress items being counted.
*/
public void progressCount(int nCount) {
fireProgressCount(nCount);
}
/**
* Indicate that progress has been updated.
*
* @param int nIncrement, the current position of the progress in relation to the inital count
* @param String sMessage, the message to display to the user
*/
public void progressUpdate(int nIncrement, String sMessage) {
fireProgressUpdate(nIncrement, sMessage);
}
/**
* Indicate that progress has complete.
*/
public void progressComplete() {
fireProgressComplete();
}
/**
* Indicate that progress has had a problem.
*
* @param String sMessage, the message to display to the user.
*/
public void progressAlert(String sMessage) {
fireProgressAlert(sMessage);
}
// PROGRESS LISTENER EVENTS
/**
* Adds <code>DBProgressListener</code> to listeners notified when progress events happen.
*
* @see #removeProgressListener
* @see #removeAllProgressListeners
* @see #fireProgressCount
* @see #fireProgressUpdate
* @see #fireProgressComplete
* @see #fireProgressAlert
*/
public void addProgressListener(DBProgressListener listener) {
if (listener == null) return;
if (!progressListeners.contains(listener)) {
progressListeners.addElement(listener);
}
}
/**
* Removes <code>DBProgressListener</code> from listeners notified of progress events.
*
* @see #addProgressListener
* @see #removeAllProgressListeners
* @see #fireProgressCount
* @see #fireProgressUpdate
* @see #fireProgressComplete
* @see #fireProgressAlert
*/
public void removeProgressListener(DBProgressListener listener) {
if (listener == null) return;
progressListeners.removeElement(listener);
}
/**
* Removes all listeners notified about progress events.
*
* @see #addProgressListener
* @see #removeProgressListener
* @see #fireProgressCount
* @see #fireProgressUpdate
* @see #fireProgressComplete
* @see #fireProgressAlert
*/
public void removeAllProgressListeners() {
progressListeners.clear();
}
/**
* Notifies progress listeners of the total count of progress events.
*
* @see #fireProgressUpdate
* @see #fireProgressComplete
* @see #fireProgressAlert
* @see #addProgressListener
* @see #removeProgressListener
* @see #removeAllProgressListeners
*/
protected void fireProgressCount(int nCount) {
for (Enumeration e = progressListeners.elements(); e.hasMoreElements(); ) {
DBProgressListener listener = (DBProgressListener) e.nextElement();
listener.progressCount(nCount);
}
}
/**
* Notifies progress listeners about progress change.
*
* @see #fireProgressCount
* @see #fireProgressComplete
* @see #fireProgressAlert
* @see #addProgressListener
* @see #removeProgressListener
* @see #removeAllProgressListeners
*/
protected void fireProgressUpdate(int nIncrement, String sMessage) {
for (Enumeration e = progressListeners.elements(); e.hasMoreElements(); ) {
DBProgressListener listener = (DBProgressListener) e.nextElement();
listener.progressUpdate(nIncrement, sMessage);
}
}
/**
* Notifies progress listeners about progress completion.
*
* @see #fireProgressCount
* @see #fireProgressUpdate
* @see #fireProgressAlert
* @see #addProgressListener
* @see #removeProgressListener
* @see #removeAllProgressListeners
*/
protected void fireProgressComplete() {
for (Enumeration e = progressListeners.elements(); e.hasMoreElements(); ) {
DBProgressListener listener = (DBProgressListener) e.nextElement();
listener.progressComplete();
}
}
/**
* Notifies progress listeners about progress alert.
*
* @see #fireProgressCount
* @see #fireProgressUpdate
* @see #fireProgressComplete
* @see #addProgressListener
* @see #removeProgressListener
* @see #removeAllProgressListeners
* @see #removeAllProgressListeners
*/
protected void fireProgressAlert(String sMessage) {
for (Enumeration e = progressListeners.elements(); e.hasMoreElements(); ) {
DBProgressListener listener = (DBProgressListener) e.nextElement();
listener.progressAlert(sMessage);
}
}
}