View Javadoc

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