View Javadoc

1   package com.melloware.jukes.db;
2   
3   import java.io.File;
4   import java.sql.Connection;
5   import java.sql.SQLException;
6   import java.sql.Statement;
7   
8   import org.apache.commons.io.FileUtils;
9   import org.apache.commons.io.FilenameUtils;
10  import org.apache.commons.logging.Log;
11  import org.apache.commons.logging.LogFactory;
12  import org.hsqldb.Server;
13  import org.hsqldb.server.ServerConstants;
14  
15  import com.melloware.jukes.exception.InfrastructureException;
16  
17  /**
18   * Static class used to start and stop the HSQLDB Server which will be run in
19   * process in the same JVM but can be connected to externally by other
20   * applications.
21   * <p>
22   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
23   * @author Emil A. Lefkof III <info@melloware.com>
24   * @version 4.0
25   * @see org.hsqldb.Server AZ 2010
26   */
27  public final class Database {
28  
29     private static final Log LOG = LogFactory.getLog(Database.class);
30     private static Server server = null;
31     private static String jdbcUrl = null;
32  
33     /**
34      * Private constructor to prevent instantiation.
35      */
36     private Database() {
37        super();
38     }
39  
40     /**
41      * Gets the connection URL for this database.
42      * <p>
43      * @return the string connection URL for JDBC drivers
44      */
45     public static String getJdbcURL() {
46        if (LOG.isInfoEnabled()) {
47           LOG.info("JDBC URL: " + jdbcUrl);
48        }
49        return jdbcUrl;
50     }
51  
52     /**
53      * But HSQLDB has a hidden catch; the write delay. By default, HSQLDB has a
54      * write delay for all activity of 60 seconds. I'd forgotten this myself when
55      * developing the example for this article and was running short lived tests
56      * and was surprised to find empty databases on disk. You can of course turn
57      * off the write delay feature completely, or tune it to something more
58      * appropriate to your application's life cycle.
59      * <p>
60      * @param connection the JDBC connection to use
61      * @param writeDelay the write delay either TRUE, FALSE, or a number in
62      *           seconds
63      */
64     public static void setWriteDelay(final Connection connection, final String writeDelay) {
65        Statement stmt = null;
66        try {
67           stmt = connection.createStatement();
68           final String sql = "SET WRITE_DELAY " + writeDelay + ";";
69           LOG.debug(sql);
70           stmt.execute(sql);
71        } catch (SQLException ex) {
72           LOG.error(ex.getMessage(), ex);
73        } finally {
74           if (stmt != null) {
75              try {
76                 stmt.close();
77              } catch (SQLException ex) {
78                 LOG.error(ex.getMessage(), ex);
79                 throw new InfrastructureException(ex);
80              }
81           }
82        }
83  
84     }
85  
86     /**
87      * Back up this database to a zipped file at location aLocation.
88      * <p>
89      * @param aConnection the connection to use
90      * @param aZipLocation the location to back up to
91      */
92     public static void backupDatabase(final Connection aConnection, final String aZipLocation) {
93        if (LOG.isInfoEnabled()) {
94           LOG.info("Backing up database to " + aZipLocation);
95        }
96  
97        // first call CHECKPOINT to get the database in a proper state for
98        // archiving
99        executeSQL(aConnection, "CHECKPOINT DEFRAG");
100       // AZ: create archive file name
101       final String aTarLocation = aZipLocation + ".tar.gz";
102       final File tarFile = new File(aTarLocation);
103 
104       try {
105          // create the directory structure if it does not exist
106          final String path = FilenameUtils.getFullPathNoEndSeparator(aZipLocation);
107          FileUtils.forceMkdir(new File(path));
108          // AZ: delete tar-file if exists
109          if (tarFile.exists()) {
110             FileUtils.forceDelete(tarFile);
111          }
112          // AZ: create TAR.GZ database archive including jukes.data,
113          // jukes.properties, jukes.script
114          executeSQL(aConnection, "BACKUP DATABASE TO '" + aTarLocation + "' COMPRESSED BLOCKING");
115       } catch (Exception ex) {
116          throw new InfrastructureException(ex);
117       }
118 
119       /**
120        * AZ: old archiving procedure. not used for HSQLDB 2.0 // now zip the
121        * file up to the location specified final String location =
122        * databaseLocation; final String[] filesToZip = new String[4];
123        * filesToZip[0] = location + ".backup"; filesToZip[1] = location +
124        * ".properties"; filesToZip[2] = location + ".script"; // dummy zero
125        * length file just as placeholder for restoring filesToZip[3] =
126        * databaseAlias + ".data"; final byte[] buffer = new byte[18024]; try {
127        * final File zipFile = new File(aZipLocation); // create the directory
128        * structure if it does not exist if (!zipFile.exists()) { final String
129        * path = FilenameUtils.getFullPathNoEndSeparator(aZipLocation);
130        * FileUtils.forceMkdir(new File(path)); } final ZipOutputStream out = new
131        * ZipOutputStream(new FileOutputStream(zipFile)); // set the compression
132        * ratio out.setLevel(Deflater.BEST_COMPRESSION); // iterate through the
133        * array adding each file to the zip for (int i = 0; i <
134        * filesToZip.length; i++) { final String filename = filesToZip[i];
135        * LOG.info("Zipping " + filename); // add to the zip out.putNextEntry(new
136        * ZipEntry(FilenameUtils.getName(filename))); File file = new
137        * File(filename); FileInputStream in = null; if (file.exists()) { in =
138        * new FileInputStream(file); // transfer bytes from the current file to
139        * the zip int len; while ((len = in.read(buffer)) > 0) {
140        * out.write(buffer, 0, len); } // close the current file input stream
141        * in.close(); } // close the current entry out.closeEntry(); } // close
142        * the ZipOutputStream out.close(); } catch (Exception ex) { throw new
143        * InfrastructureException(ex); }
144        **/
145 
146    }
147 
148    /**
149     * Executes a SQL string against the connection.
150     * <p>
151     * @param connection the connection to use
152     * @param sql the sql string to execute
153     */
154    public static void executeSQL(final Connection connection, final String sql) {
155       if (LOG.isDebugEnabled()) {
156          LOG.debug("Executing SQL: " + sql);
157       }
158 
159       Statement stmt = null;
160       try {
161          stmt = connection.createStatement();
162          LOG.debug(sql);
163          stmt.execute(sql);
164       } catch (SQLException ex) {
165          LOG.error(ex.getMessage(), ex);
166          throw new InfrastructureException(ex);
167       } finally {
168          if (stmt != null) {
169             try {
170                stmt.close();
171             } catch (SQLException ex) {
172                LOG.error(ex.getMessage(), ex);
173                throw new InfrastructureException(ex);
174             }
175          }
176       }
177    }
178 
179    /**
180     * Shuts the database down.
181     */
182    public static void shutdown() {
183       shutdownHSQLDB();
184    }
185 
186    /**
187     * Starts the database up.
188     */
189    public static void startup(final String location, final String alias) {
190       startupHSQLDB(location, alias);
191    }
192 
193    /**
194     * Shuts down the HSQLDB that is running in process.
195     */
196    private static void shutdownHSQLDB() {
197       try {
198          if (server != null) {
199             LOG.info("Shutting down HSQLDB Server.");
200             server.stop();
201             while (server.getState() != ServerConstants.SERVER_STATE_SHUTDOWN) {
202                try {
203                   LOG.debug("Sleeping waiting for server to stop");
204                   Thread.sleep(100);
205                } catch (InterruptedException e) {
206                   LOG.debug("Interrupted");
207                }
208             }
209             server = null;
210          }
211 
212       } catch (RuntimeException ex) {
213          LOG.error(ex.getMessage(), ex);
214          throw new InfrastructureException(ex);
215       }
216    }
217 
218    /**
219     * Starts the HSQLDB server in process at the location specified.
220     * <p>
221     * @param location the location to create the new database on disk
222     * @param alias the alias to give the DB
223     */
224    private static void startupHSQLDB(final String location, final String alias) {
225       try {
226          LOG.info("Starting up HSQLDB Server.");
227          jdbcUrl = "jdbc:hsqldb:hsql://localhost/" + alias;
228          server = new Server();
229 
230          if (LOG.isInfoEnabled()) {
231             LOG.info("Location: " + location);
232             LOG.info(server.getProductName() + " " + server.getProductVersion());
233          }
234 
235          server.setDatabaseName(0, alias);
236          server.setDatabasePath(0, "file:" + location + ";runtime.gc_interval=10000;hsqldb.default_table_type=cached;");
237          server.setLogWriter(null);
238          server.setErrWriter(null);
239          server.setSilent(true);
240          server.setTrace(false);
241          server.start();
242 
243          System.getProperties().put("db.product", server.getProductName());
244          System.getProperties().put("db.version", server.getProductVersion());
245          System.getProperties().put("db.address", server.getAddress());
246 
247       } catch (RuntimeException ex) {
248          LOG.error(ex.getMessage(), ex);
249          throw new InfrastructureException(ex);
250       }
251    }
252 
253 }