You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
381 lines
14 KiB
381 lines
14 KiB
/* |
|
* 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
|
|
|