//$Header: /cvsroot-fuse/mec-as2/39/mendelson/comm/as2/database/DBServer.java,v 1.1 2012/04/18 14:10:29 heller Exp $ package de.mendelson.comm.as2.database; import de.mendelson.comm.as2.AS2ServerVersion; import de.mendelson.comm.as2.preferences.PreferencesAS2; import de.mendelson.comm.as2.server.AS2Server; import de.mendelson.comm.as2.server.UpgradeRequiredException; import de.mendelson.util.MecResourceBundle; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.MissingResourceException; import java.util.Properties; import java.util.ResourceBundle; import java.util.logging.Logger; import org.hsqldb.Server; import org.hsqldb.persist.HsqlProperties; import org.hsqldb.server.ServerConstants; /* * Copyright (C) mendelson-e-commerce GmbH Berlin Germany * * This software is subject to the license agreement set forth in the license. * Please read and agree to all terms before using this software. * Other product and brand names are trademarks of their respective owners. */ /** * Class to start a dedicated SQL database server * @author S.Heller * @version $Revision: 1.1 $ * @since build 70 */ public class DBServer { /**Resourcebundle to localize messages of the DB server*/ private static MecResourceBundle rb; /**Log messages*/ private Logger logger = Logger.getLogger(AS2Server.SERVER_LOGGER_NAME); /**Database object*/ private Server server = null; private PreferencesAS2 preferences = new PreferencesAS2(); static { try { rb = (MecResourceBundle) ResourceBundle.getBundle( ResourceBundleDBServer.class.getName()); } //load up resourcebundle catch (MissingResourceException e) { throw new RuntimeException("Oops..resource bundle " + e.getClassName() + " not found."); } } /**Start a dedicated database server */ public DBServer() throws Exception { //split up database if its an older version with a single DB this.createDeprecatedCheck(); //check if hsqldb 2.x is used or an older version this.checkDBUpgradeRequired(); } private void checkDBUpgradeRequired() throws UpgradeRequiredException, Exception { File propertiesFileConfig = new File(DBDriverManager.getDBName(DBDriverManager.DB_CONFIG) + ".properties"); File propertiesFileRuntime = new File(DBDriverManager.getDBName(DBDriverManager.DB_RUNTIME) + ".properties"); String versionConfig = ""; String versionRuntime = ""; if (propertiesFileConfig.exists()) { Properties dbProperties = new Properties(); FileInputStream inStream = null; try { inStream = new FileInputStream(propertiesFileConfig.getAbsolutePath()); dbProperties.load(inStream); } finally { if (inStream != null) { inStream.close(); } } versionConfig = dbProperties.getProperty("version"); } if (propertiesFileRuntime.exists()) { Properties dbProperties = new Properties(); FileInputStream inStream = null; try { inStream = new FileInputStream(propertiesFileRuntime.getAbsolutePath()); dbProperties.load(inStream); } finally { if (inStream != null) { inStream.close(); } } versionRuntime = dbProperties.getProperty("version"); } if (versionConfig.startsWith("1") || versionRuntime.startsWith("1")) { throw new UpgradeRequiredException(this.rb.getResourceString("upgrade.required")); } } /**Starts an internal DB server with default parameter*/ private void startDBServer() throws Exception { this.server = new Server(); //start an internal server int dbPort = this.preferences.getInt(PreferencesAS2.SERVER_DB_PORT); this.server.setPort(dbPort); this.server.setDatabasePath(0, DBDriverManager.getDBName(DBDriverManager.DB_CONFIG)); this.server.setDatabaseName(0, DBDriverManager.getDBAlias(DBDriverManager.DB_CONFIG)); this.server.setDatabasePath(1, DBDriverManager.getDBName(DBDriverManager.DB_RUNTIME)); this.server.setDatabaseName(1, DBDriverManager.getDBAlias(DBDriverManager.DB_RUNTIME)); HsqlProperties hsqlProperties = new HsqlProperties(); hsqlProperties.setProperty("hsqldb.cache_file_scale", 128); hsqlProperties.setProperty("hsqldb.write_delay", false); hsqlProperties.setProperty("hsqldb.write_delay_millis", 0); this.server.setProperties(hsqlProperties); this.server.setLogWriter(null); this.server.start(); } public void startup() throws Exception { this.startDBServer(); try { this.createCheck(); } catch (Exception e) { this.logger.warning(e.getMessage()); } try { DBServer.defragDB(DBDriverManager.DB_CONFIG); } catch (Exception e) { this.logger.warning(e.getMessage()); } try { DBServer.defragDB(DBDriverManager.DB_RUNTIME); } catch (Exception e) { this.logger.warning(e.getMessage()); } try { Connection configConnection = DBDriverManager.getLocalConnection(DBDriverManager.DB_CONFIG); if (configConnection == null) { return; } Statement statement = configConnection.createStatement(); statement.execute("SET FILES SCRIPT FORMAT COMPRESSED"); statement.close(); //check if a DB update is necessary. If so, update the DB this.updateDB(configConnection, DBDriverManager.DB_CONFIG); configConnection.close(); Connection runtimeConnection = DBDriverManager.getLocalConnection(DBDriverManager.DB_RUNTIME); if (runtimeConnection == null) { return; } statement = runtimeConnection.createStatement(); statement.execute("SET FILES SCRIPT FORMAT COMPRESSED"); statement.close(); //check if a runtime DB update is necessary. If so, update the runtime DB this.updateDB(runtimeConnection, DBDriverManager.DB_RUNTIME); DatabaseMetaData data = runtimeConnection.getMetaData(); this.logger.info(rb.getResourceString("server.started", new Object[]{data.getDatabaseProductName() + " " + data.getDatabaseProductVersion()})); runtimeConnection.close(); } catch (Exception e) { this.logger.severe("DBServer.startup: " + e.getMessage()); } DBDriverManager.setupConnectionPool(); } /**Performs a defragementation of the passed database. This is necessary to keep the database files small * */ public static void defragDB(final int DB_TYPE) throws Exception { Connection connection = null; Statement statement = null; try { connection = DBDriverManager.getConnectionWithoutErrorHandling(DB_TYPE, "localhost"); statement = connection.createStatement(); statement.execute("CHECKPOINT DEFRAG"); } finally { try { if (statement != null) { statement.close(); } } catch (Exception e) { Logger.getLogger(AS2Server.SERVER_LOGGER_NAME).warning(e.getMessage()); } try { if (statement != null) { connection.close(); } } catch (Exception e) { Logger.getLogger(AS2Server.SERVER_LOGGER_NAME).warning(e.getMessage()); } } } /**Check if db exists and create a new one * if it doesnt exist */ private void createCheck() { if (!this.databaseExists(DBDriverManager.DB_CONFIG)) { //new installation DBDriverManager.createDatabase(DBDriverManager.DB_CONFIG); } if (!this.databaseExists(DBDriverManager.DB_RUNTIME)) { //new installation DBDriverManager.createDatabase(DBDriverManager.DB_RUNTIME); } } /**Returns if the passed database type exists*/ private boolean databaseExists(int databaseType) { String TABLE_NAME = "TABLE_NAME"; String[] TABLE_TYPES = {"TABLE"}; boolean databaseFound = false; Connection connection = null; try { connection = DBDriverManager.getConnectionWithoutErrorHandling(databaseType, "localhost"); if (connection != null) { DatabaseMetaData metadata = connection.getMetaData(); ResultSet tableResultRuntime = metadata.getTables(null, null, null, TABLE_TYPES); while (tableResultRuntime.next()) { if (tableResultRuntime.getString(TABLE_NAME).equalsIgnoreCase("version")) { databaseFound = true; } } connection.close(); } } catch (Exception e) { return (databaseFound); } finally { if (connection != null) { try { connection.close(); } catch (Exception e) { //nop } } } return (databaseFound); } private int getActualDBVersion(Connection connection) { Statement statement = null; int foundVersion = -1; ResultSet result = null; try { statement = connection.createStatement(); statement.setEscapeProcessing(true); result = statement.executeQuery("SELECT MAX(actualversion) AS maxversion FROM version"); if (result.next()) { //value is always in the first column foundVersion = result.getInt("maxversion"); } } catch (SQLException e) { Logger.getLogger(AS2Server.SERVER_LOGGER_NAME).warning(e.getMessage()); } finally { if (result != null) { try { result.close(); } catch (SQLException ex) { Logger.getLogger(AS2Server.SERVER_LOGGER_NAME).warning(ex.getMessage()); } } if (statement != null) { try { statement.close(); } catch (SQLException ex) { Logger.getLogger(AS2Server.SERVER_LOGGER_NAME).warning(ex.getMessage()); } } } return (foundVersion); } /**Update the database if this is necessary. *@param connection connection to the database *@param DB_TYPE of the database that should be created, as defined in this class MecDriverManager */ private void updateDB(Connection connection, final int DB_TYPE) { int requiredDBVersion = -1; String dbName = null; if (DB_TYPE == DBDriverManager.DB_CONFIG) { dbName = "config"; requiredDBVersion = AS2ServerVersion.getRequiredDBVersionConfig(); } else if (DB_TYPE == DBDriverManager.DB_RUNTIME) { dbName = "runtime"; requiredDBVersion = AS2ServerVersion.getRequiredDBVersionRuntime(); } else if (DB_TYPE != DBDriverManager.DB_DEPRICATED) { throw new RuntimeException("Unknown DB type requested in DBServer:updateDB."); } int foundVersion = this.getActualDBVersion(connection); //check if this is smaller than the required version! if (foundVersion != -1 && foundVersion < requiredDBVersion) { this.logger.info(rb.getResourceString("update.versioninfo", new Object[]{ String.valueOf(foundVersion), String.valueOf(requiredDBVersion) })); this.logger.info(rb.getResourceString("update.progress")); for (int i = foundVersion; i < requiredDBVersion; i++) { this.logger.info(rb.getResourceString("update.progress.version.start", new Object[]{String.valueOf(i + 1), dbName})); if (!this.startDBUpdate(i, connection, DB_TYPE)) { this.logger.info(rb.getResourceString("update.error", new Object[]{String.valueOf(i), String.valueOf(i + 1)})); System.exit(-1); } //set new version to the database this.setNewDBVersion(connection, i + 1); int newActualVersion = this.getActualDBVersion(connection); this.logger.info(rb.getResourceString("update.progress.version.end", new Object[]{String.valueOf(newActualVersion), dbName})); } this.logger.info((rb.getResourceString("update.successfully", dbName))); } } /**Sets the new DB version to the passed number if the update was *successfully *@param connection DB connection to use *@param version new DB version the update has updated to */ private void setNewDBVersion(Connection connection, int version) { try { //request all connections from the database to store them PreparedStatement statement = connection.prepareStatement( "INSERT INTO version(actualVersion,updateDate,updateComment)VALUES(?,?,?)"); statement.setEscapeProcessing(true); //fill in values statement.setInt(1, version); statement.setDate(2, new java.sql.Date(System.currentTimeMillis())); statement.setString(3, "by " + AS2ServerVersion.getFullProductName() + " auto updater"); statement.execute(); statement.close(); } catch (SQLException e) { this.logger.warning("DBServer.setNewDBVersion: " + e); } } /**Sends a shutdown signal to the DB*/ public void shutdown() { try { Connection configConnection = DBDriverManager.getConnectionWithoutErrorHandling(DBDriverManager.DB_CONFIG, "localhost"); Connection runtimeConnection = DBDriverManager.getConnection(DBDriverManager.DB_RUNTIME, "localhost"); configConnection.createStatement().execute("SHUTDOWN"); configConnection.close(); System.out.println("DB server: config DB shutdown complete."); runtimeConnection.createStatement().execute("SHUTDOWN"); runtimeConnection.close(); System.out.println("DB server: runtime DB shutdown complete."); } catch (Exception e) { System.out.println("DB server shutdown: " + e.getMessage()); } try { this.server.signalCloseAllServerConnections(); } catch (Exception e) { System.out.println(e.getMessage()); } try { DBDriverManager.shutdownConnectionPool(); } catch (Exception e) { System.out.println(e.getMessage()); } this.server.shutdown(); while (this.server.getState() != ServerConstants.SERVER_STATE_SHUTDOWN) { try { Thread.sleep(1000); } catch (Exception e) { } } String shutdownMessage = "DB server: shut down complete."; System.out.println(shutdownMessage); } /**Start the DB update from the startVersion to the startVersion+1 *@param startVersion Start version *@param connection Connection to use for the update *@return true if the update was successful *@param DB_TYPE of the database that should be created, as defined in this class MecDriverManager */ private boolean startDBUpdate(int startVersion, Connection connection, final int DB_TYPE) { boolean updatePerformed = false; String updateResource = null; if (DB_TYPE == DBDriverManager.DB_CONFIG) { updateResource = SQLScriptExecutor.SCRIPT_RESOURCE_CONFIG; } else if (DB_TYPE == DBDriverManager.DB_RUNTIME) { updateResource = SQLScriptExecutor.SCRIPT_RESOURCE_RUNTIME; } else if (DB_TYPE != DBDriverManager.DB_DEPRICATED) { throw new RuntimeException("Unknown DB type requested in DBServer."); } //sql file to execute for the update process String sqlResource = updateResource + "update" + startVersion + "to" + (startVersion + 1) + ".sql"; SQLScriptExecutor executor = new SQLScriptExecutor(); try { //defrag the DB DBServer.defragDB(DB_TYPE); if (executor.resourceExists(sqlResource)) { executor.executeScript(connection, sqlResource); updatePerformed = true; } //check if a java file should be executed that changes something in //the database, too String javaUpdateClass = updateResource.replace('/', '.') + "Update" + startVersion + "to" + (startVersion + 1); if (javaUpdateClass.startsWith(".")) { javaUpdateClass = javaUpdateClass.substring(1); } Class cl = Class.forName(javaUpdateClass); IUpdater updater = (IUpdater) cl.newInstance(); updater.startUpdate(connection); if (!updater.updateWasSuccessfully()) { throw new Exception("Update failed."); } } catch (ClassNotFoundException e) { //ignore if update is already ok if (!updatePerformed) { this.logger.info("DBServer.startDBUpdate (ClassNotFoundException):" + e); this.logger.info(rb.getResourceString("update.notfound", new Object[]{String.valueOf(startVersion), String.valueOf(startVersion + 1), updateResource })); return (false); } else { return (true); } } catch (Throwable e) { this.logger.warning(e.getMessage()); return (false); } return (true); } /**Split up the DB into a config and a runtime database if this is an AS version where only a single database * exists (< end of 2011) */ private void createDeprecatedCheck() throws Exception { File deprecatedFile = new File(DBDriverManager.getDBName(DBDriverManager.DB_DEPRICATED) + ".script"); File configFile = new File(DBDriverManager.getDBName(DBDriverManager.DB_CONFIG) + ".script"); File runtimeFile = new File(DBDriverManager.getDBName(DBDriverManager.DB_RUNTIME) + ".script"); //create new Database if (deprecatedFile.exists() && !configFile.exists() && !runtimeFile.exists()) { this.logger.info("Performing database split into config/runtime database."); //update issue, performed on 11/2011: split up deprecated database this.copyDeprecatedDatabaseTo(DBDriverManager.getDBName(DBDriverManager.DB_CONFIG)); this.copyDeprecatedDatabaseTo(DBDriverManager.getDBName(DBDriverManager.DB_RUNTIME)); this.logger.info("Database structure splitted."); } } /**Splits up the depricated database into 2 separate databases. The version of these splitted databases could be any from *0 to 50.*/ private void copyDeprecatedDatabaseTo(String targetBase) throws IOException { String sourceBase = DBDriverManager.getDBName(DBDriverManager.DB_DEPRICATED); this.copyFile(sourceBase + ".backup", targetBase + ".backup"); this.copyFile(sourceBase + ".data", targetBase + ".data"); this.copyFile(sourceBase + ".properties", targetBase + ".properties"); this.copyFile(sourceBase + ".script", targetBase + ".script"); } /**Copies the contents from one stream to the other */ public void copyStreams(InputStream input, OutputStream output) throws IOException { byte[] buffer = new byte[4096]; int read = 0; while (read != -1) { read = input.read(buffer); if (read > 0) { output.write(buffer, 0, read); output.flush(); } } } private void copyFile(String source, String target) throws IOException { File sourceFile = new File(source); if (!sourceFile.exists()) { return; } FileInputStream inStream = new FileInputStream(sourceFile); FileOutputStream outStream = new FileOutputStream(target); this.copyStreams(inStream, outStream); inStream.close(); outStream.close(); } }