001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.jdbc;
023:
024: import java.awt.HeadlessException;
025: import java.io.File;
026: import java.io.IOException;
027: import java.lang.reflect.Method;
028: import java.sql.Connection;
029: import java.sql.Driver;
030: import java.sql.DriverManager;
031: import java.sql.Statement;
032:
033: import org.jboss.system.ServiceMBeanSupport;
034: import org.jboss.system.server.ServerConfigLocator;
035:
036: /**
037: * Integration with <a href="http://sourceforge.net/projects/hsqldb">HSQLDB</a>
038: *
039: * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Öberg</a>
040: * @author <a href="mailto:Scott_Stark@displayscape.com">Scott Stark</a>.
041: * @author <a href="mailto:pf@iprobot.com">Peter Fagerlund</a>
042: * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
043: * @author <a href="mailto:vesco.claudio@previnet.it">Claudio Vesco</a>
044: * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
045: * @version $Revision: 63201 $
046: */
047: public class HypersonicDatabase extends ServiceMBeanSupport implements
048: HypersonicDatabaseMBean {
049: /** Default password: <code>empty string</code>. */
050: private static final String DEFAULT_PASSWORD = "";
051:
052: /** Default user: <code>sa</code>. */
053: private static final String DEFAULT_USER = "sa";
054:
055: /** JDBC Driver class: <code>org.hsqldb.jdbcDriver</code>. */
056: private static final String JDBC_DRIVER_CLASS = "org.hsqldb.jdbcDriver";
057:
058: /** JDBC URL common prefix: <code>jdbc:hsqldb:</code>. */
059: private static final String JDBC_URL_PREFIX = "jdbc:hsqldb:";
060:
061: /** Default shutdown command for remote hypersonic: <code>SHUTDOWN COMPACT</code>. */
062: private static final String DEFAULT_REMOTE_SHUTDOWN_COMMAND = "SHUTDOWN COMPACT";
063:
064: /** Default shutdown command for in process persist hypersonic: <code>SHUTDOWN COMPACT</code>. */
065: private static final String DEFAULT_IN_PROCESS_SHUTDOWN_COMMAND = "SHUTDOWN COMPACT";
066:
067: /** Default shutdown command for in process only memory hypersonic: <code>SHUTDOWN IMMEDIATELY</code>. */
068: private static final String DEFAULT_IN_MEMORY_SHUTDOWN_COMMAND = "SHUTDOWN IMMEDIATELY";
069:
070: /** Default data subdir: <code>hypersonic</code>. */
071: private static final String HYPERSONIC_DATA_DIR = "hypersonic";
072:
073: /** Default port for remote hypersonic: <code>1701</code>. */
074: private static final int DEFAULT_PORT = 1701;
075:
076: /** Default address for remote hypersonic: <code>0.0.0.0</code>. */
077: private static final String DEFAULT_ADDRESS = "0.0.0.0";
078:
079: /** Default database name: <code>default</code>. */
080: private static final String DEFAULT_DATABASE_NAME = "default";
081:
082: /** Database name for memory-only hypersonic: <code>.</code>. */
083: private static final String IN_MEMORY_DATABASE = ".";
084:
085: /** Default database manager (UI) class: <code>org.hsqldb.util.DatabaseManagerSwing</code>. */
086: private static final String DEFAULT_DATABASE_MANAGER_CLASS = "org.hsqldb.util.DatabaseManagerSwing";
087:
088: /** Default server class for remote hypersonic: <code>org.hsqldb.Server</code>. */
089: private static final String DEFAULT_SERVER_CLASS = "org.hsqldb.Server";
090:
091: // Private Data --------------------------------------------------
092:
093: /** Full path to db/hypersonic. */
094: private File dbPath;
095:
096: /** Database name. */
097: private String name = DEFAULT_DATABASE_NAME;
098:
099: /** Default port. */
100: private int port = DEFAULT_PORT;
101:
102: /** Default address. */
103: private String address = DEFAULT_ADDRESS;
104:
105: /** Default silent. */
106: private boolean silent = true;
107:
108: /** Default trace. */
109: private boolean trace = false;
110:
111: /** Default no_system_exit, new embedded support in 1.7 */
112: private boolean no_system_exit = true;
113:
114: /** Default persisted DB */
115: private boolean persist = true;
116:
117: /** Shutdown command. */
118: private String shutdownCommand;
119:
120: /** In process/remote mode. */
121: private boolean inProcessMode = false;
122:
123: /** Database user. */
124: private String user = DEFAULT_USER;
125:
126: /** Database password. */
127: private String password = DEFAULT_PASSWORD;
128:
129: /** Database manager (UI) class. */
130: private String databaseManagerClass = DEFAULT_DATABASE_MANAGER_CLASS;
131:
132: /** Server class for remote hypersonic. */
133: private String serverClass = DEFAULT_SERVER_CLASS;
134:
135: /** Server thread for remote hypersonic. */
136: private Thread serverThread;
137:
138: /** Hold a connection for in process hypersonic. */
139: private Connection connection;
140:
141: // Constructors --------------------------------------------------
142:
143: /**
144: * Costructor, empty.
145: */
146: public HypersonicDatabase() {
147: // empty
148: }
149:
150: // Attributes ----------------------------------------------------
151:
152: /**
153: * Set the database name.
154: *
155: * @jmx.managed-attribute
156: */
157: public void setDatabase(String name) {
158: if (name == null) {
159: name = DEFAULT_DATABASE_NAME;
160: }
161: this .name = name;
162: }
163:
164: /**
165: * Get the database name.
166: *
167: * @jmx.managed-attribute
168: */
169: public String getDatabase() {
170: return name;
171: }
172:
173: /**
174: * Set the port for remote hypersonic.
175: *
176: * @jmx.managed-attribute
177: */
178: public void setPort(final int port) {
179: this .port = port;
180: }
181:
182: /**
183: * Get the port for remote hypersonic.
184: *
185: * @jmx.managed-attribute
186: */
187: public int getPort() {
188: return port;
189: }
190:
191: /**
192: * Set the bind address for remote hypersonic.
193: *
194: * @jmx.managed-attribute
195: */
196: public void setBindAddress(final String address) {
197: this .address = address;
198: }
199:
200: /**
201: * Get the bind address for remote hypersonic.
202: *
203: * @jmx.managed-attribute
204: */
205: public String getBindAddress() {
206: return address;
207: }
208:
209: /**
210: * Set silent flag.
211: *
212: * @jmx.managed-attribute
213: */
214: public void setSilent(final boolean silent) {
215: this .silent = silent;
216: }
217:
218: /**
219: * Get silent flag.
220: *
221: * @jmx.managed-attribute
222: */
223: public boolean getSilent() {
224: return silent;
225: }
226:
227: /**
228: * Set trace flag.
229: *
230: * @jmx.managed-attribute
231: */
232: public void setTrace(final boolean trace) {
233: this .trace = trace;
234: }
235:
236: /**
237: * Get trace flag.
238: *
239: * @jmx.managed-attribute
240: */
241: public boolean getTrace() {
242: return trace;
243: }
244:
245: /**
246: * If <b>true</b> the server thread for remote hypersonic does no call <code>System.exit()</code>.
247: *
248: * @jmx.managed-attribute
249: */
250: public void setNo_system_exit(final boolean no_system_exit) {
251: this .no_system_exit = no_system_exit;
252: }
253:
254: /**
255: * Get the <code>no_system_exit</code> flag.
256: *
257: * @jmx.managed-attribute
258: */
259: public boolean getNo_system_exit() {
260: return no_system_exit;
261: }
262:
263: /**
264: * Set persist flag.
265: *
266: * @deprecated use {@link #setInProcessMode(boolean)(boolean) inProcessMode}.
267: *
268: * @jmx.managed-attribute
269: */
270: public void setPersist(final boolean persist) {
271: this .persist = persist;
272: }
273:
274: /**
275: * Get persist flag.
276: *
277: * @deprecated use {@link #setInProcessMode(boolean)(boolean) inProcessMode}.
278: *
279: * @jmx.managed-attribute
280: */
281: public boolean getPersist() {
282: return persist;
283: }
284:
285: /**
286: * Get the full database path.
287: *
288: * @jmx.managed-attribute
289: */
290: public String getDatabasePath() {
291: if (dbPath != null) {
292: return dbPath.toString();
293: } else {
294: return null;
295: }
296: }
297:
298: /**
299: * @return the <code>inProcessMode</code> flag.
300: *
301: * @jmx.managed-attribute
302: */
303: public boolean isInProcessMode() {
304: return inProcessMode;
305: }
306:
307: /**
308: * @return the shutdown command.
309: *
310: * @jmx.managed-attribute
311: */
312: public String getShutdownCommand() {
313: return shutdownCommand;
314: }
315:
316: /**
317: * If <b>true</b> the hypersonic is in process mode otherwise hypersonic is in server or remote mode.
318: *
319: * @param b in process mode or remote mode.
320: *
321: * @jmx.managed-attribute
322: */
323: public void setInProcessMode(boolean b) {
324: inProcessMode = b;
325: }
326:
327: /**
328: * @param string the shutdown command
329: *
330: * @jmx.managed-attribute
331: */
332: public void setShutdownCommand(String string) {
333: shutdownCommand = string;
334: }
335:
336: /**
337: * @return the password
338: *
339: * @jmx.managed-attribute
340: */
341: public String getPassword() {
342: return password;
343: }
344:
345: /**
346: * @return the user
347: *
348: * @jmx.managed-attribute
349: */
350: public String getUser() {
351: return user;
352: }
353:
354: /**
355: * @param password
356: *
357: * @jmx.managed-attribute
358: */
359: public void setPassword(String password) {
360: if (password == null) {
361: password = DEFAULT_PASSWORD;
362: }
363: this .password = password;
364: }
365:
366: /**
367: * @param user
368: *
369: * @jmx.managed-attribute
370: */
371: public void setUser(String user) {
372: if (user == null) {
373: user = DEFAULT_USER;
374: }
375: this .user = user;
376: }
377:
378: /**
379: * @return
380: *
381: * @jmx.managed-attribute
382: */
383: public String getDatabaseManagerClass() {
384: return databaseManagerClass;
385: }
386:
387: /**
388: * Set the database manager (UI) class.
389: *
390: * @param databaseManagerClass
391: *
392: * @jmx.managed-attribute
393: */
394: public void setDatabaseManagerClass(String databaseManagerClass) {
395: if (databaseManagerClass == null) {
396: databaseManagerClass = DEFAULT_DATABASE_MANAGER_CLASS;
397: }
398: this .databaseManagerClass = databaseManagerClass;
399: }
400:
401: /**
402: * @return server class for remote hypersonic.
403: */
404: public String getServerClass() {
405: return serverClass;
406: }
407:
408: /**
409: * Set the server class for remote hypersonic.
410: *
411: * @param serverClass
412: */
413: public void setServerClass(String serverClass) {
414: if (serverClass == null) {
415: serverClass = DEFAULT_SERVER_CLASS;
416: }
417: this .serverClass = serverClass;
418: }
419:
420: // Operations ----------------------------------------------------
421:
422: /**
423: * Start of DatabaseManager accesible from the management console.
424: *
425: * @jmx.managed-operation
426: */
427: public void startDatabaseManager() {
428: // Start DBManager in new thread
429: new Thread() {
430: public void run() {
431: try {
432: // If bind address is the default 0.0.0.0, use localhost
433: String connectHost = DEFAULT_ADDRESS
434: .equals(address) ? "localhost" : address;
435: String driver = JDBC_DRIVER_CLASS;
436: String[] args;
437: if (!inProcessMode) {
438: args = new String[] {
439: "-noexit",
440: "-driver",
441: driver,
442: "-url",
443: JDBC_URL_PREFIX + "hsql://"
444: + connectHost + ":" + port,
445: "-user", user, "-password", password,
446: "-dir", getDatabasePath() };
447: } else if (IN_MEMORY_DATABASE.equals(name)) {
448: args = new String[] { "-noexit", "-driver",
449: driver, "-url",
450: JDBC_URL_PREFIX + IN_MEMORY_DATABASE,
451: "-user", user, "-password", password };
452: } else {
453: args = new String[] { "-noexit", "-driver",
454: driver, "-url",
455: JDBC_URL_PREFIX + getDatabasePath(),
456: "-user", user, "-password", password,
457: "-dir", getDatabasePath() };
458: }
459:
460: // load (and link) the class only if needed
461: ClassLoader cl = Thread.currentThread()
462: .getContextClassLoader();
463: Class clazz = Class.forName(databaseManagerClass,
464: true, cl);
465: Method main = clazz.getMethod("main",
466: new Class[] { args.getClass() });
467: main.invoke(null, new Object[] { args });
468: } catch (HeadlessException e) {
469: log
470: .error("Failed to start database manager because this is a headless configuration (no display, mouse or keyword)");
471: } catch (java.lang.reflect.InvocationTargetException ite) {
472:
473: // The headless exception may be wrapped inside the InvocationTargetException. Check for it...
474: if (ite.getCause() instanceof HeadlessException) {
475: log
476: .error("Failed to start database manager because this is a headless configuration (no display, mouse or keyword)");
477: }
478: } catch (Exception e) {
479: log.error("Failed to start database manager", e);
480: }
481: }
482: }.start();
483: }
484:
485: // Lifecycle -----------------------------------------------------
486:
487: /**
488: * Start the database
489: */
490: protected void startService() throws Exception {
491: // check persist for old compatibility
492: if (!persist) {
493: inProcessMode = true;
494: name = IN_MEMORY_DATABASE;
495: }
496:
497: // which database?
498: if (!inProcessMode) {
499: startRemoteDatabase();
500: } else if (IN_MEMORY_DATABASE.equals(name)) {
501: startInMemoryDatabase();
502: } else {
503: startStandaloneDatabase();
504: }
505: }
506:
507: /**
508: * We now close the connection clean by calling the
509: * serverSocket throught jdbc. The MBeanServer calls this
510: * method at closing time.
511: */
512: protected void stopService() throws Exception {
513: // which database?
514: if (!inProcessMode) {
515: stopRemoteDatabase();
516: } else if (IN_MEMORY_DATABASE.equals(name)) {
517: stopInMemoryDatabase();
518: } else {
519: stopStandaloneDatabase();
520: }
521: }
522:
523: // Private -------------------------------------------------------
524:
525: /**
526: * Start the standalone (in process) database.
527: */
528: private void startStandaloneDatabase() throws Exception {
529: // Get the server data directory
530: File dataDir = ServerConfigLocator.locate().getServerDataDir();
531:
532: // Get DB directory
533: File hypersoniDir = new File(dataDir, HYPERSONIC_DATA_DIR);
534:
535: if (!hypersoniDir.exists()) {
536: hypersoniDir.mkdirs();
537: }
538:
539: if (!hypersoniDir.isDirectory()) {
540: throw new IOException("Failed to create directory: "
541: + hypersoniDir);
542: }
543:
544: dbPath = new File(hypersoniDir, name);
545:
546: String dbURL = JDBC_URL_PREFIX + getDatabasePath();
547:
548: // hold a connection so hypersonic does not close the database
549: connection = getConnection(dbURL);
550: }
551:
552: /**
553: * Start the only in memory database.
554: */
555: private void startInMemoryDatabase() throws Exception {
556: String dbURL = JDBC_URL_PREFIX + IN_MEMORY_DATABASE;
557:
558: // hold a connection so hypersonic does not close the database
559: connection = getConnection(dbURL);
560: }
561:
562: /**
563: * Start the remote database.
564: */
565: private void startRemoteDatabase() throws Exception {
566: // Get the server data directory
567: File dataDir = ServerConfigLocator.locate().getServerDataDir();
568:
569: // Get DB directory
570: File hypersoniDir = new File(dataDir, HYPERSONIC_DATA_DIR);
571:
572: if (!hypersoniDir.exists()) {
573: hypersoniDir.mkdirs();
574: }
575:
576: if (!hypersoniDir.isDirectory()) {
577: throw new IOException("Failed to create directory: "
578: + hypersoniDir);
579: }
580:
581: dbPath = new File(hypersoniDir, name);
582:
583: // Start DB in new thread, or else it will block us
584: serverThread = new Thread("hypersonic-" + name) {
585: public void run() {
586: try {
587: // Create startup arguments
588: String[] args = new String[] { "-database",
589: dbPath.toString(), "-port",
590: String.valueOf(port), "-address", address,
591: "-silent", String.valueOf(silent),
592: "-trace", String.valueOf(trace),
593: "-no_system_exit",
594: String.valueOf(no_system_exit), };
595:
596: // Start server
597: ClassLoader cl = Thread.currentThread()
598: .getContextClassLoader();
599: Class clazz = Class.forName(serverClass, true, cl);
600: Method main = clazz.getMethod("main",
601: new Class[] { args.getClass() });
602: main.invoke(null, new Object[] { args });
603: } catch (Exception e) {
604: log.error("Failed to start database", e);
605: }
606: }
607: };
608: serverThread.start();
609: }
610:
611: /**
612: * Stop the standalone (in process) database.
613: */
614: private void stopStandaloneDatabase() throws Exception {
615: String dbURL = JDBC_URL_PREFIX + getDatabasePath();
616:
617: Connection connection = getConnection(dbURL);
618: Statement statement = connection.createStatement();
619:
620: String shutdownCommand = this .shutdownCommand;
621: if (shutdownCommand == null) {
622: shutdownCommand = DEFAULT_IN_PROCESS_SHUTDOWN_COMMAND;
623: }
624:
625: statement.executeQuery(shutdownCommand);
626: this .connection = null;
627: log.info("Database standalone closed clean");
628: }
629:
630: /**
631: * Stop the in memory database.
632: */
633: private void stopInMemoryDatabase() throws Exception {
634: String dbURL = JDBC_URL_PREFIX + IN_MEMORY_DATABASE;
635:
636: Connection connection = getConnection(dbURL);
637: Statement statement = connection.createStatement();
638:
639: String shutdownCommand = this .shutdownCommand;
640: if (shutdownCommand == null) {
641: shutdownCommand = DEFAULT_IN_MEMORY_SHUTDOWN_COMMAND;
642: }
643:
644: statement.executeQuery(shutdownCommand);
645: this .connection = null;
646: log.info("Database in memory closed clean");
647: }
648:
649: /**
650: * Stop the remote database.
651: */
652: private void stopRemoteDatabase() throws Exception {
653: // If bind address is the default 0.0.0.0, use localhost
654: String connectHost = DEFAULT_ADDRESS.equals(address) ? "localhost"
655: : address;
656: String dbURL = JDBC_URL_PREFIX + "hsql://" + connectHost + ":"
657: + port;
658:
659: Connection connection = getConnection(dbURL);
660: Statement statement = connection.createStatement();
661:
662: String shutdownCommand = this .shutdownCommand;
663: if (shutdownCommand == null) {
664: shutdownCommand = DEFAULT_REMOTE_SHUTDOWN_COMMAND;
665: }
666:
667: statement.executeQuery(shutdownCommand);
668: // TODO: join thread?
669: serverThread = null;
670: this .connection = null;
671: log.info("Database remote closed clean");
672: }
673:
674: /**
675: * Get the connection.
676: *
677: * @param dbURL jdbc url.
678: * @return the connection, allocate one if needed.
679: * @throws Exception
680: */
681: private synchronized Connection getConnection(String dbURL)
682: throws Exception {
683: if (connection == null) {
684: ClassLoader cl = Thread.currentThread()
685: .getContextClassLoader();
686: Class.forName(JDBC_DRIVER_CLASS, true, cl).newInstance();
687: connection = DriverManager.getConnection(dbURL, user,
688: password);
689: }
690: return connection;
691: }
692:
693: }
|