/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 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 Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.platform.web.hsqldb; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hsqldb.Server; import org.hsqldb.server.ServerConfiguration; import org.hsqldb.server.ServerConstants; import org.hsqldb.server.ServerProperties; import org.hsqldb.lib.FileUtil; import org.hsqldb.persist.HsqlProperties; import org.pentaho.platform.web.hsqldb.messages.Messages; import java.io.IOException; import java.net.ServerSocket; import java.text.MessageFormat; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.Map; /** * The purpose of this Java class is to startup a HSQLDB databases. This class should not installed in production * environments. This is for samples / demoing only. * * You typically will configure a ServletContext parameter in your web.xml, named "hsqldb-databases" with a value that * follows this format: dbName@../path/to/database,@otherDbName@../path/to/other/database * */ public class HsqlDatabaseStarterBean { private static final Log logger = LogFactory.getLog( HsqlDatabaseStarterBean.class ); private Server hsqlServer; private int port = 9001; // Default Port private int failoverPort = port; private Map<String, String> databases = new LinkedHashMap<String, String>(); private boolean allowFailoverToDefaultPort; protected boolean checkPort() { if ( ( port < 0 ) || ( port > 65535 ) ) { if ( allowFailoverToDefaultPort ) { logger.error( Messages.getErrorString( "HsqlDatabaseStarterBean.ERROR_0004_INVALID_PORT", "" + failoverPort ) ); //$NON-NLS-1$ port = failoverPort; } else { return logFailure( "HsqlDatabaseStarterBean.ERROR_0004_INVALID_PORT", "" + failoverPort ); //$NON-NLS-1$ } } try { ServerSocket sock = new ServerSocket( port ); sock.close(); } catch ( IOException ex1 ) { if ( port == failoverPort ) { return logFailure( "HsqlDatabaseStarterBean.ERROR_0006_DEFAULT_PORT_IN_USE" ); //$NON-NLS-1$ } else { if ( allowFailoverToDefaultPort ) { logger.error( Messages.getErrorString( "HsqlDatabaseStarterBean.ERROR_0005_SPECIFIED_PORT_IN_USE", Integer.toString( port ), "" + failoverPort ) ); //$NON-NLS-1$ port = failoverPort; try { ServerSocket sock = new ServerSocket( port ); sock.close(); } catch ( IOException ex2 ) { return logFailure( "HsqlDatabaseStarterBean.ERROR_0006_DEFAULT_PORT_IN_USE" ); //$NON-NLS-1$ } } else { return logFailure( "HsqlDatabaseStarterBean.ERROR_0008_SPECIFIED_PORT_IN_USE_NO_FAILOVER", Integer.toString( port ) ); //$NON-NLS-1$ } } } return true; } // Facilitate test cases protected HsqlProperties getServerProperties( String[] args ) { // From HSQLDB Server.java main method... String propsPath = FileUtil.getFileUtil().canonicalOrAbsolutePath( "server" ); //$NON-NLS-1$ HsqlProperties fileProps = ServerConfiguration.getPropertiesFromFile( ServerConstants.SC_PROTOCOL_HSQL, propsPath, ".properties" ); HsqlProperties props = fileProps == null ? new HsqlProperties() : fileProps; HsqlProperties stringProps = null; try { stringProps = HsqlProperties.argArrayToProps( args, "server" ); props.addProperties( stringProps ); } catch ( ArrayIndexOutOfBoundsException ex ) { logger.error( Messages.getErrorString( "HsqlDatabaseStarterBean.ERROR_0001_INVALID_PARAMETERS" ) ); //$NON-NLS-1$ ex.printStackTrace(); logger.warn( Messages.getString( "HsqlDatabaseStarterBean.WARN_NO_DATABASES" ) ); //$NON-NLS-1$ return null; } return props; } // Facilitate test cases protected Server getNewHSQLDBServer() { return new Server(); } /** * Starts hsqldb databases. * * @return true if the server was started properly. */ public boolean start() { if ( !checkPort() ) { return false; } ArrayList<String> startupArguments = getStartupArguments(); String[] args = startupArguments.toArray( new String[] {} ); if ( logger.isTraceEnabled() ) { logger.trace( "Assembled parameters" ); //$NON-NLS-1$ for ( int i = 0; i < args.length; i++ ) { logger.trace( String.format( " args[%d]=%s", i, args[i] ) ); //$NON-NLS-1$ } } HsqlProperties props = getServerProperties( args ); if ( props == null ) { // If props failed, return return false; } hsqlServer = getNewHSQLDBServer(); try { hsqlServer.setProperties( props ); } catch ( Exception e ) { logger.error( Messages.getErrorString( "HsqlDatabaseStarterBean.ERROR_0002_INVALID_CONFIGURATION" ) ); //$NON-NLS-1$ e.printStackTrace(); logger.warn( Messages.getString( "HsqlDatabaseStarterBean.WARN_NO_DATABASES" ) ); //$NON-NLS-1$ return false; } hsqlServer.start(); return hsqlServer.getState() == ServerConstants.SERVER_STATE_ONLINE; } /** * Stops the hsqldb databases. * * @return true if the server stopped properly. */ public boolean stop() { if ( hsqlServer != null ) { try { logger.debug( "Stopping embedded hsqldb databases" ); //$NON-NLS-1$ logger.debug( "Signaling connection close..." ); //$NON-NLS-1$ hsqlServer.signalCloseAllServerConnections(); logger.debug( "Stopping server listener threads.." ); //$NON-NLS-1$ hsqlServer.stop(); int times = 0; logger.debug( "Waiting for embedded server to complete shut down tasks..." ); //$NON-NLS-1$ // Give it about 15 or so seconds to quit... while ( ( hsqlServer.getState() != ServerConstants.SERVER_STATE_SHUTDOWN ) && ( times < 100 ) ) { try { Thread.sleep( times + 1 * 100 ); } catch ( InterruptedException e ) { //ignore } times++; } if ( hsqlServer.getState() != ServerConstants.SERVER_STATE_SHUTDOWN ) { logger.error( Messages.getErrorString( "HsqlDatabaseStarterBean.ERROR_0003_DID_NOT_STOP" ) ); //$NON-NLS-1$ return false; } return true; } finally { hsqlServer = null; } } return true; } private boolean logFailure( final String errorId ) { logger.error( Messages.getErrorString( errorId ) ); logger.warn( Messages.getString( "HsqlDatabaseStarterBean.WARN_NO_DATABASES" ) ); //$NON-NLS-1$ return false; } private boolean logFailure( final String errorId, String param ) { logger.error( Messages.getErrorString( errorId, param ) ); logger.warn( Messages.getString( "HsqlDatabaseStarterBean.WARN_NO_DATABASES" ) ); //$NON-NLS-1$ return false; } protected ArrayList<String> getStartupArguments() { ArrayList<String> rtnArgsList = new ArrayList<String>(); if ( port != 9001 ) { rtnArgsList.add( "-port" ); //$NON-NLS-1$ rtnArgsList.add( Integer.toString( port ) ); } // Prevent system.exit(0); rtnArgsList.add( "-no_system_exit" ); //$NON-NLS-1$ rtnArgsList.add( "true" ); //$NON-NLS-1$ int idx = 0; for ( Map.Entry<String, String> entry : databases.entrySet() ) { rtnArgsList.add( "-database." + idx ); //$NON-NLS-1$ rtnArgsList.add( entry.getValue() ); rtnArgsList.add( "-dbname." + idx ); //$NON-NLS-1$ rtnArgsList.add( entry.getKey() ); logger.debug( MessageFormat.format( "Hsqldb database {0} configured to start with name {1}", entry.getValue(), entry.getKey() ) ); //$NON-NLS-1$ idx++; } return rtnArgsList; } /* * Getters and Setters */ public void setPort( int value ) { port = value; } public int getPort() { return port; } public void setFailoverPort( int value ) { failoverPort = value; } public int getFailoverPort() { return failoverPort; } public Map<String, String> getDatabases() { return databases; } public void setDatabases( Map<String, String> databases ) { this.databases = databases; } public void setAllowPortFailover( boolean value ) { allowFailoverToDefaultPort = value; } public boolean getAllowPortFailover() { return allowFailoverToDefaultPort; } }