Java tutorial
/* * Adito * * Copyright (C) 2003-2006 3SP LTD. All Rights Reserved * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package com.adito.jdbc.hsqldb; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.net.Socket; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hsqldb.HsqlProperties; import org.hsqldb.Server; import org.hsqldb.ServerConstants; import com.adito.boot.ContextHolder; import com.adito.jdbc.JDBCConnectionImpl; /** * <p> * Maintains the embedded HSQLDB server engine. When Adito is started up, * an instance is created and then {@link #start()}ed. * * <p> * Plugins may then register any new databases they wish to add to the server * (although this should be called indirectly through * {@link com.adito.core.CoreServlet#addDatabase(String,File)}. * * <p> * When the server is shutting down, the {@link #stop()} method should be called * allow the database engine to clean itself up. */ public class EmbeddedHSQLDBServer { private final static Log log = LogFactory.getLog(EmbeddedHSQLDBServer.class); private HsqlProperties properties; private Server server; private boolean testedConnection; private boolean serverMode; private int dbIdx = 1; private List<String> databases; private boolean started; /** * Constructor * * @param serverMode if <code>true</code> run HSQLDB in <b>Server</b> * mode, which allows external TCP/IP connections. * @throws Exception */ public EmbeddedHSQLDBServer(boolean serverMode) throws Exception { super(); databases = new ArrayList<String>(); this.serverMode = serverMode; if (serverMode) { properties = new HsqlProperties(); } } /** * Stop the Database engine. */ public void stop() { if (server != null) { /* * TODO A nasty hack. HSQLDB cannot have new databases added to it * while its running in TCP/IP server mode. So we have to restart * the server. Unfortunately, the client side of the connection does * not register that this has happened so is considered re-useable * by the pool. This results in a 'Connection is closed' error when * then next statement executes. */ JDBCConnectionImpl.JDBCPool.getInstance().closeAll(); // Get a JDBC connection for (Iterator i = databases.iterator(); i.hasNext();) { String n = (String) i.next(); Connection con = null; try { if (log.isInfoEnabled()) log.info("Compacting database " + n); con = DriverManager.getConnection( EmbeddedHSQLDBServer.this.serverMode ? "jdbc:hsqldb:hsql://localhost/" + n : "jdbc:hsqldb:file:" + ContextHolder.getContext().getDBDirectory().getPath() + "/" + n); Statement s = con.createStatement(); s.execute("SHUTDOWN COMPACT"); if (log.isInfoEnabled()) log.info("Database " + n + " compacted."); } catch (Exception e) { log.error("Failed to compact database."); } finally { if (con != null) { try { con.close(); } catch (Exception e) { } } } } server.signalCloseAllServerConnections(); server.stop(); waitForServerToStop(); server = null; testedConnection = false; } started = false; } /** * Start the database engine. * * @throws Exception */ public void start() throws Exception { if (serverMode) { if (server == null) { server = new Server(); server.setLogWriter(log.isDebugEnabled() ? new PrintWriter(new LoggingPrintWriter()) : new PrintWriter(new SinkPrintWiter())); server.setNoSystemExit(true); server.setProperties(properties); } if (server.getState() != ServerConstants.SERVER_STATE_SHUTDOWN) { throw new Exception("Cannot start an HSQLDB server that is not shutdown."); } server.start(); } waitForServer(); started = true; } void waitForServer() { if (!testedConnection && serverMode) { Socket s = null; String addr = server.getAddress().equals("0.0.0.0") ? "127.0.0.1" : server.getAddress(); if (log.isInfoEnabled()) log.info("Waiting for HSQLDB to start accepting connections on " + addr + ":" + server.getPort()); for (int i = 0; i < 30; i++) { try { s = new Socket(addr, server.getPort()); break; } catch (IOException ioe) { try { Thread.sleep(1000); } catch (InterruptedException ie) { } } } if (s == null) { throw new IllegalStateException("The HSQLDB server is not accepting connections after 30 seconds."); } else { testedConnection = true; if (log.isInfoEnabled()) log.info("HSQLDB is now accepting connections."); try { s.close(); } catch (IOException ioe) { } } } } void waitForServerToStop() { if (serverMode) { Socket s = null; String addr = server.getAddress().equals("0.0.0.0") ? "127.0.0.1" : server.getAddress(); if (log.isInfoEnabled()) log.info("Waiting for HSQLDB to stop accepting connections on " + addr + ":" + server.getPort()); int i = 0; for (; i < 30; i++) { try { s = new Socket(addr, server.getPort()); try { s.close(); } catch (Exception e) { e.printStackTrace(); } s = null; try { Thread.sleep(1000); } catch (InterruptedException ie) { } } catch (IOException ioe) { break; } finally { if (s != null) { try { s.close(); } catch (Exception e) { } s = null; } } } if (i == 30) { throw new IllegalStateException("The HSQLDB server has not stopped after 30 seconds."); } else { if (log.isInfoEnabled()) log.info("HSQLDB is now stopped."); } } } /** * Add a new database to be maintained by this engine. If the database files * do not already exist they will be automatically created. If running in * TCP/IP server mode and the database has not been yet been started it will * be. * * @param databaseName * @param file * @throws Exception on any error */ public void addDatabase(String databaseName, File file) throws Exception { if (!databases.contains(databaseName)) { if (serverMode) { if (log.isInfoEnabled()) log.info("Adding database " + databaseName + " in TCP/IP server mode, so restarting database"); boolean wasStarted = started; if (wasStarted) { stop(); } databases.add(databaseName); dbIdx++; properties.setProperty("server.database." + dbIdx, "file:" + file.getPath() + "/" + databaseName); properties.setProperty("server.dbname." + dbIdx, databaseName); start(); } else { if (log.isInfoEnabled()) log.info("Adding database " + databaseName + " in embedded mode."); databases.add(databaseName); } } } /* * Dummy {@link Write} to just sink log output. */ class SinkPrintWiter extends Writer { public void close() throws IOException { } public void flush() throws IOException { } public void write(char[] cbuf, int off, int len) throws IOException { } } /* * {@link Writer} to just convert log output in Commons Loggin calls. */ class LoggingPrintWriter extends Writer { private StringBuffer buffer = new StringBuffer(); private char ch; /* * (non-Javadoc) * * @see java.io.Writer#close() */ public void close() throws IOException { // not implemented. } /* * (non-Javadoc) * * @see java.io.Writer#flush() */ public void flush() throws IOException { } /* * (non-Javadoc) * * @see java.io.Writer#write(char[], int, int) */ public synchronized void write(char[] cbuf, int off, int len) throws IOException { String s = new String(cbuf, off, len); for (int i = 0; i < len; i++) { ch = s.charAt(i); if (ch == '\n') { if (log.isInfoEnabled()) log.info(buffer.toString()); buffer.setLength(0); } else { buffer.append(ch); } } } } }