001: package com.technoetic.xplanner.db.hsqldb;
002:
003: import java.io.File;
004: import java.io.IOException;
005: import java.sql.SQLException;
006: import java.util.regex.Matcher;
007: import java.util.regex.Pattern;
008:
009: import org.apache.log4j.Logger;
010: import org.hsqldb.DatabaseURL;
011: import org.hsqldb.Server;
012: import org.hsqldb.ServerConstants;
013: import org.springframework.jdbc.core.JdbcTemplate;
014: import org.springframework.jdbc.datasource.SingleConnectionDataSource;
015:
016: import com.technoetic.xplanner.XPlannerProperties;
017: import com.technoetic.xplanner.db.hibernate.HibernateHelper;
018: import com.technoetic.xplanner.util.LogUtil;
019:
020: /** @noinspection UseOfSystemOutOrSystemErr,EmptyCatchBlock*/
021:
022: public class HsqlServer {
023: public static final Logger LOG = LogUtil.getLogger();
024: public static final String DATABASE_NAME = "xplanner";
025:
026: public static HsqlServer theServer;
027:
028: public static final String HSQLDB_DB_PATH = "xplanner.hsqldb.server.database";
029: public static final String HSQLDB_URL = XPlannerProperties.CONNECTION_URL_KEY;
030: public static final String WEBAPP_ROOT_TOKEN = "${WEBAPP_ROOT}";
031: public static final String WEBAPP_ROOT_TOKEN_PATTERN_STRING = "\\$\\{WEBAPP_ROOT\\}";
032:
033: public static final String FILE_URL_PREFIX = DatabaseURL.S_URL_PREFIX
034: + DatabaseURL.S_FILE;
035: public static final String HSQLDB_FILE_URL_PATTERN_STRING = FILE_URL_PREFIX
036: + ":([a-zA-Z0-9/\\\\]+)";
037: public static final Pattern HSQLDB_FILE_URL_PATTERN = Pattern
038: .compile(HSQLDB_FILE_URL_PATTERN_STRING);
039: public static final String HSQL_URL_PREFIX = DatabaseURL.S_URL_PREFIX
040: + DatabaseURL.S_HSQL;
041: public static final String HSQLDB_HSQL_URL_PATTERN_STRING = HSQL_URL_PREFIX
042: + "([a-zA-Z0-9.]*)(:([0-9]+))?/([a-zA-Z0-9]+)";
043: public static final Pattern HSQLDB_HSQL_URL_PATTERN = Pattern
044: .compile(HSQLDB_HSQL_URL_PATTERN_STRING);
045: public static final String ABSOLUTE_PATH_PATTERN_STRING = "^([a-zA-Z]:|/|\\\\).*";
046: public static final Pattern ABSOLUTE_PATH_PATTERN = Pattern
047: .compile(ABSOLUTE_PATH_PATTERN_STRING);
048:
049: private String webRootPath = "";
050: private Server server;
051: private String databaseName;
052: private String dbPath;
053: private int port = 9001;
054:
055: public static void start() {
056: start("");
057: }
058:
059: public static void start(String webRoot) {
060: XPlannerProperties properties = new XPlannerProperties();
061: if (!isHsqldb(properties))
062: return;
063:
064: theServer = new HsqlServer();
065: theServer.initProperties(webRoot, properties);
066: registerShutdownHook();
067:
068: if (isEmbeddedPrivateDatabase(properties)) {
069: LOG
070: .info("HSQL: detected an embedded database, in-process public server not started");
071: return;
072: }
073: if (isRemoteDatabase(properties)) {
074: LOG
075: .info("HSQL: detected a remote database, in-process server not started");
076: return;
077: }
078: if (isLocalOutOfProcessDatabase(properties)) {
079: LOG
080: .info("HSQL: detected a local out-of-process database, in-process server not started");
081: return;
082: }
083: theServer.startPublicServer();
084: }
085:
086: private static void registerShutdownHook() {
087: Runtime.getRuntime().addShutdownHook(new Thread() {
088: public void run() {
089: shutdown();
090: }
091: });
092: }
093:
094: public void startPublicServer() {
095: if (dbPath == null) {
096: throw new RuntimeException(
097: HSQLDB_DB_PATH
098: + " property is required in order to start an in-process HSQLDB server");
099: }
100:
101: LOG.info("Starting HSQLDB server for db " + databaseName
102: + " stored at " + dbPath);
103:
104: server = createServer();
105: server.setDatabasePath(0, dbPath);
106: server.setDatabaseName(0, databaseName);
107: server.setNoSystemExit(true);
108: server.setPort(port);
109: server.setTrace(isTraceOn());
110: server.setSilent(!isTraceOn());
111: server.start();
112:
113: waitForServerToTransitionOutOf(ServerConstants.SERVER_STATE_OPENING);
114: LOG.info("HSQLDB server started");
115: }
116:
117: public void initProperties(String webRootPath,
118: XPlannerProperties properties) {
119: this .webRootPath = webRootPath;
120:
121: if (isHsqlProtocol(properties)) {
122: dbPath = getDbPath(properties);
123: if (dbPath != null) {
124: Matcher matcher = HSQLDB_HSQL_URL_PATTERN
125: .matcher(getUrl(properties));
126: if (matcher.matches()) {
127: databaseName = getDatabaseName(matcher);
128: port = getPort(matcher);
129: }
130: }
131: }
132:
133: setUrl(properties, replaceWebRootToken(getUrl(properties)));
134:
135: LOG.debug(" " + dumpPropertyValue(HSQLDB_URL, properties));
136: LOG.debug(" "
137: + dumpPropertyValue(HSQLDB_DB_PATH, properties));
138: LOG.debug(" " + dumpNameValuePair("webroot", webRootPath));
139: }
140:
141: private String dumpPropertyValue(String property,
142: XPlannerProperties properties) {
143: return dumpNameValuePair(property, properties
144: .getProperty(property));
145: }
146:
147: private String dumpNameValuePair(String name, String value) {
148: return name + "='" + value + "'";
149: }
150:
151: private static String getUrl(XPlannerProperties properties) {
152: return properties.getProperty(HSQLDB_URL);
153: }
154:
155: private static void setUrl(XPlannerProperties properties,
156: String value) {
157: properties.setProperty(HSQLDB_URL, value);
158: }
159:
160: private String getDatabaseName(Matcher matcher) {
161: String name = matcher.group(4);
162: if (name == null)
163: return DATABASE_NAME;
164: return name;
165: }
166:
167: private int getPort(Matcher matcher) {
168: String port = matcher.group(3);
169: if (port == null)
170: return 9001;
171: return Integer.parseInt(port);
172: }
173:
174: public static boolean isHsqldb(XPlannerProperties properties) {
175: return properties.getProperty(HSQLDB_URL, "").startsWith(
176: DatabaseURL.S_URL_PREFIX);
177: }
178:
179: public static boolean isLocalPublicDatabaseStarted() {
180: return theServer != null
181: && theServer.server != null
182: && theServer.server.getState() == ServerConstants.SERVER_STATE_ONLINE;
183: }
184:
185: private static boolean isLocalOutOfProcessDatabase(
186: XPlannerProperties properties) {
187: return isLocalHsqlProtocolDatabase(properties)
188: && !isEmbeddedDatabaseSpecified(properties);
189: }
190:
191: private static boolean isRemoteDatabase(
192: XPlannerProperties properties) {
193: return !isLocalHsqlProtocolDatabase(properties)
194: && isHsqlProtocol(properties);
195: }
196:
197: private static boolean isHsqlProtocol(XPlannerProperties properties) {
198: return getUrl(properties) != null
199: && getUrl(properties).startsWith(HSQL_URL_PREFIX);
200: }
201:
202: private static boolean isEmbeddedDatabaseSpecified(
203: XPlannerProperties properties) {
204: return properties.getProperty(HSQLDB_DB_PATH) != null;
205: }
206:
207: // Could use HSQL DatabaseUrl
208: private static boolean isLocalHsqlProtocolDatabase(
209: XPlannerProperties properties) {
210: if (getUrl(properties) == null)
211: return false;
212: Matcher matcher = HSQLDB_HSQL_URL_PATTERN
213: .matcher(getUrl(properties));
214: if (!matcher.matches())
215: return false;
216: String host = matcher.group(1);
217: return "localhost".equals(host);
218: }
219:
220: private static boolean isEmbeddedPrivateDatabase(
221: XPlannerProperties properties) {
222: return !isLocalHsqlProtocolDatabase(properties)
223: || !isEmbeddedDatabaseSpecified(properties);
224: }
225:
226: private String getDbPath(XPlannerProperties properties) {
227: String dbPath = properties.getProperty(HSQLDB_DB_PATH);
228: if (dbPath == null)
229: return null;
230: if (dbPath.startsWith(WEBAPP_ROOT_TOKEN)) {
231: return replaceWebRootToken(dbPath);
232: }
233: if (ABSOLUTE_PATH_PATTERN.matcher(dbPath).matches()) {
234: return dbPath;
235: }
236: return new File(getStartDirectory() + File.separator + dbPath)
237: .getAbsolutePath();
238: }
239:
240: private String replaceWebRootToken(String oldPath) {
241: // String prefix = oldPath.replaceFirst(WEBAPP_ROOT_TOKEN_PATTERN_STRING, webRootPath); // TODO: Once in JDK 1.5 try this one again, in 1.4 it get a StringIndexOutOfBoundsEx if the webroot is "C:\\Projects\\xplanner-trunk\\build\\deploy\\"
242: int tokenStartPos = oldPath.indexOf(WEBAPP_ROOT_TOKEN);
243: int semiCommaPos = oldPath.indexOf(';');
244: if (tokenStartPos == -1)
245: return oldPath;
246: int tokenEndPos = tokenStartPos + WEBAPP_ROOT_TOKEN.length()
247: + 1; // THe slash in ${WEBAPP_ROOT}/
248: String prefix = oldPath.substring(0, tokenStartPos);
249: String suffix = "";
250: String oldFilePath = "";
251: if (semiCommaPos != -1) {
252: oldFilePath = oldPath.substring(tokenEndPos, semiCommaPos);
253: suffix = oldPath.substring(semiCommaPos);
254: } else {
255: oldFilePath = oldPath.substring(tokenEndPos);
256: }
257: String filePath = "";
258: if (webRootPath != null) {
259: filePath += webRootPath;
260: if (!webRootPath.endsWith("\\")
261: && !webRootPath.endsWith("/")) {
262: filePath += File.separator;
263: }
264: }
265: filePath += oldFilePath;
266: return prefix + new File(filePath).getAbsolutePath() + suffix;
267: }
268:
269: private String getStartDirectory() {
270: try {
271: return new File(".").getCanonicalPath();
272: } catch (IOException e) {
273: throw new RuntimeException(e);
274: }
275: }
276:
277: protected Server createServer() {
278: return new Server();
279: }
280:
281: private static boolean isTraceOn() {
282: return Boolean.valueOf(
283: new XPlannerProperties()
284: .getProperty("hibernate.show_sql"))
285: .booleanValue();
286: }
287:
288: public static void shutdown() {
289: if (theServer != null) {
290: theServer.stop();
291: }
292: }
293:
294: public void stop() {
295: LOG.info("Stopping HSQLDB server");
296: sendShutdownCommand();
297:
298: if (server != null) {
299: server.stop();
300: waitForServerToTransitionOutOf(ServerConstants.SERVER_STATE_CLOSING);
301: server = null;
302: }
303: theServer = null;
304: LOG.info("HSQLDB server stopped");
305: }
306:
307: private void sendShutdownCommand() {
308: JdbcTemplate template = new JdbcTemplate(
309: new SingleConnectionDataSource(HibernateHelper
310: .getConnection(), false));
311: template.execute("SHUTDOWN");
312: }
313:
314: private void waitForServerToTransitionOutOf(int serverState) {
315: while (server.getState() == serverState) {
316: try {
317: Thread.sleep(100);
318: } catch (InterruptedException e) {
319: }
320: }
321: }
322:
323: public static void main(String[] args) throws SQLException {
324: start();
325: }
326:
327: public Server getServer() {
328: return server;
329: }
330:
331: public String getDbPath() {
332: return dbPath;
333: }
334:
335: public static HsqlServer getInstance() {
336: return theServer;
337: }
338:
339: }
|