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 }