/* * Copyright (c) 2001-2004, The HSQL Development Group All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * 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 HSQL DEVELOPMENT GROUP, HSQLDB.ORG, 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 de.memtext.db; import java.awt.Component; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; import java.util.ResourceBundle; import javax.swing.JOptionPane; /** * A class which helps managing multiple connections to a stand-alone database * (e.g. on a network path). After one user has opened the database other users * can get the option to open a temporary copy of the database. The temporary * copy may be read-only, but doesn't has to be. If it's not read-only all, all * changes will be discarded when the connection is closed. (May be useful if * the application only does some dispensible logging or so). see * SampleApplication */ public class HsqlStandaloneMgr { private static AbstractHsqlStandaloneMgrResources resources; private HsqlStandaloneMgr() { } private static void initResources() { String mypackage = ""; //if you place the Resources classes in some package, define it like this mypackage = "de.memtext.db."; resources = (AbstractHsqlStandaloneMgrResources) ResourceBundle.getBundle(mypackage + "HsqlStandaloneMgrResources"); } /** * Checks if a database is already opened (by checking if a .lck file * exists) Use from HSQLDB 1.7.2 or higher * * @param String * path - null,"" or "." for current directory * @param String * databasename * @return true if database is already opened */ public static boolean isDatabaseOpen(String path, String databasename) { File lckFile = new File(getAdaptedPath(path) + databasename + ".lck"); return lckFile.exists(); } /** * Deletes any temporary files which the HsqlStandaloneMgr may have created * for the given url * * @param String * url * @param String * path - null,"" or "." for current dir * @param String * databasename - of the original database, no _TMP_COPY appendix */ public static void deleteTmpFiles(String url, String path, String databasename) { if (databasename.indexOf("_TMP_COPY") > -1) throw new IllegalArgumentException("Please specifiy the name of the original database without _TMP_COPY"); path = getAdaptedPath(path); int tmpPos = url.indexOf("_TMP_COPY"); if (tmpPos == -1) { //if the main connection is closed delete info about the user File f = new File(path + databasename + "_user.properties"); if (f.exists()) f.delete(); } else { //delete files for temp. connection String tmp = url.substring(tmpPos); if (path == null || path.equals("")) path = "."; File fpath = new File(path); File tmpFiles[] = fpath.listFiles(new TmpFileFilter(databasename + tmp)); for (int i = 0; i < tmpFiles.length; i++) { tmpFiles[i].delete(); } } } /** * Asks the user if he/she wants to open a temporary copy of the database or * not. A boolean indicates if the questions concerns read-only mode or not. * * @param Component * parentComponent - usually the application Frame, but may be * null * @param String * databasename - name of original database * @param boolean * isReadOnlyCopyWanted - should the temp. copy be read-only * @return int from JOptionPane.showConfirmDialog */ public static int askUser(Component parentComponent, String path, String databasename, boolean isReadOnlyCopyWanted) { if (resources == null) initResources(); String username = null; File f = new File(getAdaptedPath(path) + databasename + "_user.properties"); if (f.exists()) { Properties p = new Properties(); FileInputStream fis; try { fis = new FileInputStream(f); p.load(fis); username = p.getProperty("user"); fis.close(); } catch (Exception e) { System.err.println(resources.getString("Couldn't read user name")); e.printStackTrace(); } } StringBuffer msg = new StringBuffer(resources.getDbInUseBy(databasename, username)); if (isReadOnlyCopyWanted) msg.append(resources.getString("Would you like to open a temporary copy in read-only mode?")); else msg.append(resources.getString("Would you like to open a temporary copy?\nAttention - any changes to the database will be lost after closing the program!")); return JOptionPane.showConfirmDialog(parentComponent, msg, "HSQLDB", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); } /** * Returns a connection to a temporary copy of a database either in * read-only mode or not. * * @param Component * parentComponent - usually the application Frame, but may be * null * @param String * path - null,"" or "." for current dir * @param String * databasename * @param String * username * @param String * password * @param boolean * isReadOnlyCopyWanted - should the temporary copy of the * database be in read-only mode * @return Connection to the temporary copy of the database * @throws ClassNotFoundException * @throws SQLException * @throws IOException */ public static Connection getTmpConnection(Component parentComponent, String path, String databasename, String username, String password, boolean isReadOnlyCopyWanted) throws ClassNotFoundException, SQLException, IOException { if (resources == null) initResources(); Class.forName("org.hsqldb.jdbcDriver"); path = getAdaptedPath(path); int tmpInstanceNumber = checkNumber(path, databasename); try { copyDatabaseFiles(path, databasename, tmpInstanceNumber); } catch (IOException e) { JOptionPane.showMessageDialog(parentComponent, resources.getString("Could not create temporary copy of database.)") + "\n" + e, "HSQLDB", JOptionPane.WARNING_MESSAGE); throw e; } if (isReadOnlyCopyWanted) { try { setReadonly(path + databasename + "_TMP_COPY" + tmpInstanceNumber + ".properties"); } catch (IOException e) { JOptionPane.showMessageDialog(parentComponent, resources.getString("Could not set temporary copy of database to readonly mode.") + "\n" + e, "HSQLDB", JOptionPane.WARNING_MESSAGE); throw e; } } String url = "jdbc:hsqldb:file:" + path + databasename + "_TMP_COPY" + tmpInstanceNumber; Connection con = java.sql.DriverManager.getConnection(url, username, password); if (!isReadOnlyCopyWanted) { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select count(*) from system_tables where hsqldb_type='TEXT'"); rs.next(); if (rs.getInt(1) > 0) { stmt.execute("shutdown"); stmt.close(); con.close(); deleteTmpFiles(url, path, databasename); throw new SQLException(resources.getString("Handling non read-only temporary database with text tables is not supported")); } rs.close(); stmt.close(); } return con; } private static int checkNumber(String path, String databasename) { int result = 1; while (new File(path + databasename + "_TMP_COPY" + result + ".script").exists()) result++; return result; } /** * Returns a regular connection to a database and creates a file * database_user.properties with the name of the user that opens the * database * * @param String * path - null,"" or "." for current dir * @param String * databasename * @param String * username * @param String * password * @return Connection to the database * @throws ClassNotFoundException * @throws SQLException * @throws IOException */ public static Connection getConnection(String path, String databasename, String username, String password) throws ClassNotFoundException, SQLException, IOException { Class.forName("org.hsqldb.jdbcDriver"); String url = null; if (path == null) url = "jdbc:hsqldb:" + databasename; else url = "jdbc:hsqldb:file:" + getAdaptedPath(path) + databasename; Connection con = java.sql.DriverManager.getConnection(url, username, password); Properties p = new Properties(); p.put("user", System.getProperty("user.name")); String filename = (path != null ? path + "/" + databasename + "_user.properties" : databasename + "_user.properties"); FileOutputStream fos = new FileOutputStream(filename); p.store(fos, "User which uses the HSQL database"); fos.close(); return con; } /** * String and makes sure separator char is at the end * * @param String * path * @return adapted path String */ private static String getAdaptedPath(String path) { if (path == null) path = ""; if ((path.equals(".") || path.length() > 1) && !(path.endsWith("/") || path.endsWith("\\"))) path += File.separator; return path; } /** * Changes the property readonly to true in a database properties file * * @param String * propertiesFile including path if necessary * @throws IOException */ private static void setReadonly(String propertiesFile) throws IOException { Properties p = new Properties(); p.load(new FileInputStream(propertiesFile)); p.put("readonly", "true"); p.store(new FileOutputStream(propertiesFile), "HSQL database"); } /** * Makes a temporary copy of all existing database files with the appendix * _TMP_COPY * * @param String * path - may be null for current dir, no / or \ at the end * @param String * databasename * @throws IOException */ private static void copyDatabaseFiles(String path, String databasename, int number) throws IOException { String s = path + databasename; copyFile(s + ".script", s + "_TMP_COPY" + number + ".script"); copyFile(s + ".properties", s + "_TMP_COPY" + number + ".properties"); if (new File(s + ".log").exists()) copyFile(s + ".log", s + "_TMP_COPY" + number + ".log"); if (new File(s + ".data").exists()) copyFile(s + ".data", s + "_TMP_COPY" + number + ".data"); if (new File(s + ".backup").exists()) copyFile(s + ".backup", s + "_TMP_COPY" + number + ".backup"); if (new File(s + ".nio").exists()) copyFile(s + ".nio", s + "_TMP_COPY" + number + ".nio"); } /** * Creates a copy of a file * * @param String * source - source file name (incl. path if necessary) * @param String * target - target file name (incl. path if necessary) * @throws IOException */ static public void copyFile(String source, String target) throws IOException { try { FileInputStream is = new FileInputStream(source); FileOutputStream os = new FileOutputStream(target); BufferedInputStream in = new BufferedInputStream(is); BufferedOutputStream out = new BufferedOutputStream(os); int buffer_size = 32768; byte[] buffer = new byte[buffer_size]; int len = in.read(buffer, 0, buffer_size); while (len != -1) { out.write(buffer, 0, len); len = in.read(buffer, 0, buffer_size); } in.close(); is.close(); out.close(); os.close(); } catch (IOException e) { throw new IOException("Couldn't copy file " + source + ": " + e.toString()); } } private static class TmpFileFilter implements FilenameFilter { private String tmpName; TmpFileFilter(String tmpName) { this.tmpName = tmpName; } @Override public boolean accept(File dir, String name) { return name.indexOf(tmpName) > -1; } } } //Created on 21.10.2004 at 20:35:55