/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.soa.bpel.runtime.db;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import org.h2.tools.Server;
import org.jboss.system.ServiceMBeanSupport;
/**
* Integration with H2.
*
* @author <a href="mailto:rickard.oberg@telkel.com">Rickard �berg</a>
* @author <a href="mailto:Scott_Stark@displayscape.com">Scott Stark</a>.
* @author <a href="mailto:pf@iprobot.com">Peter Fagerlund</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="mailto:vesco.claudio@previnet.it">Claudio Vesco</a>
* @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
* @author <a href="mailto:kevin.conner@jboss.org">Kevin Conner</a>
* @author <a href="mailto:dbevenius@redhat.com">Daniel Bevenius</a>
* @version $Revision: 27106 $
*/
public class H2Database extends ServiceMBeanSupport implements H2DatabaseMBean
{
/** Default password: <code>empty string</code>. */
private static final String DEFAULT_PASSWORD = "";
/** Default user: <code>sa</code>. */
private static final String DEFAULT_USER = "sa";
/** JDBC Driver class: <code>org.h2.Driver</code>. */
private static final String JDBC_DRIVER_CLASS = "org.h2.Driver";
/** JDBC URL common prefix: <code>jdbc:h2:</code>. */
private static final String JDBC_URL_PREFIX = "jdbc:h2:";
/** JDBC in memory URL prefix: <code>jdbc:h2:mem:</code>. */
private static final String JDBC_MEM_URL_PREFIX = JDBC_URL_PREFIX + "mem:";
/** JDBC flags */
private static final String DEFAULT_FLAGS = ";MVCC=TRUE;DB_CLOSE_ON_EXIT=FALSE";
/** Default data subdir: <code>h2</code>. */
private static final String H2_DATA_DIR = "h2";
/** Default database name: <code>default</code>. */
private static final String DEFAULT_DATABASE_NAME = "default";
/** Default address for remote h2: <code>0.0.0.0</code>. */
private static final String DEFAULT_ADDRESS = "0.0.0.0";
/** Default port for remote h2: <code>9092</code>. */
private static final int DEFAULT_PORT = 9092;
/** Default delay for remote hypersonic initialisation (ms): <code>5000</code>. */
private static final long DEFAULT_DELAY = 5000;
// Private Data --------------------------------------------------
/** Full path to db/h2. */
private File dbPath;
/** Database name. */
private String name = DEFAULT_DATABASE_NAME;
/** In memory mode. */
private boolean inMemoryMode ;
/** Database user. */
private String user = DEFAULT_USER;
/** Database password. */
private String password = DEFAULT_PASSWORD;
/** Database flags */
private String flags = DEFAULT_FLAGS ;
/** Hold a connection for in memory h2. */
private Connection connection;
/** Default address. */
private String address = DEFAULT_ADDRESS;
/** Default port. */
private int port = DEFAULT_PORT;
/** Server/remote mode. */
private boolean serverMode = false;
/** Server thread for remote h2. */
private Thread serverThread;
/** The remote server instance */
private Server remoteServer ;
/** Server thread delay for remote H2. */
private long delay = DEFAULT_DELAY ;
private String datadir;
// Attributes ----------------------------------------------------
/**
* Set the database name.
*
* @jmx.managed-attribute
*/
public void setDatabase(String name)
{
if (name == null)
{
name = DEFAULT_DATABASE_NAME;
}
this.name = name;
}
/**
* Get the database name.
*
* @jmx.managed-attribute
*/
public String getDatabase()
{
return name;
}
/**
* Get the full database path.
*
* @jmx.managed-attribute
*/
public String getDatabasePath()
{
if (dbPath != null)
{
return dbPath.toString();
}
else
{
return null;
}
}
/**
* @return the <code>inMemoryMode</code> flag.
*
* @jmx.managed-attribute
*/
public boolean isInMemoryMode()
{
return inMemoryMode;
}
/**
* If <b>true</b> the h2 is in memory mode otherwise embedded mode.
*
* @param b in memory mode.
*
* @jmx.managed-attribute
*/
public void setInMemoryMode( boolean b )
{
inMemoryMode = b;
}
/**
* @return the password
*
* @jmx.managed-attribute
*/
public String getPassword()
{
return password;
}
/**
* @return the user
*
* @jmx.managed-attribute
*/
public String getUser()
{
return user;
}
/**
* @return the flags
*
* @jmx.managed-attribute
*/
public String getFlags()
{
return flags;
}
/**
* @param password
*
* @jmx.managed-attribute
*/
public void setPassword(String password)
{
if (password == null)
{
password = DEFAULT_PASSWORD;
}
this.password = password;
}
/**
* @param user
*
* @jmx.managed-attribute
*/
public void setUser(String user)
{
if (user == null)
{
user = DEFAULT_USER;
}
this.user = user;
}
/**
* @param flags
*
* @jmx.managed-attribute
*/
public void setFlags(String flags)
{
if (flags == null)
{
flags = DEFAULT_FLAGS;
}
this.flags = flags;
}
/**
* @return the serverMode
*
* @jmx.managed-attribute
*/
public boolean isServerMode()
{
return serverMode;
}
/**
* @param serverMode
*
* @jmx.managed-attribute
*/
public void setServerMode( boolean serverMode )
{
this.serverMode = serverMode;
}
/**
* @return the address
*
* @jmx.managed-attribute
*/
public String getBindAddress()
{
return address;
}
/**
* @return the port
*
* @jmx.managed-attribute
*/
public int getPort()
{
return port;
}
/**
* @param address
*
* @jmx.managed-attribute
*/
public void setBindAddress(String address)
{
this.address = address;
}
/**
* @param port
*
* @jmx.managed-attribute
*/
public void setPort(int port)
{
this.port = port;
}
/**
* Set the delay for remote hypersonic initialisation.
*
* @jmx.managed-attribute
*/
public void setDelay(final long delay)
{
this.delay = delay;
}
/**
* Get the delay for remote hypersonic initialisation.
*
* @jmx.managed-attribute
*/
public long getDelay()
{
return delay;
}
// Lifecycle -----------------------------------------------------
/**
* Start the database
*/
protected void startService() throws Exception
{
if (serverMode)
{
startRemoteDatabase();
}
else if (inMemoryMode)
{
startInMemoryDatabase();
}
else
{
startStandaloneDatabase();
}
}
/**
* We now close the connection clean by calling the
* serverSocket throught jdbc. The MBeanServer calls this
* method at closing time.
*/
protected void stopService() throws Exception
{
if (serverMode)
{
stopRemoteDatabase();
}
else if (inMemoryMode)
{
stopInMemoryDatabase();
}
else
{
stopStandaloneDatabase();
}
}
// Private -------------------------------------------------------
/**
* Start the standalone (in process) database.
*/
private void startStandaloneDatabase() throws Exception
{
final File h2Dir = checkDataDir() ;
dbPath = new File(h2Dir, name);
final String dbURL = JDBC_URL_PREFIX + dbPath.toURI().toString() + flags ;
log.info(dbURL);
// Check we have connectivity
connection = getConnection(dbURL);
}
/**
* Start the only in memory database.
*/
private void startInMemoryDatabase() throws Exception
{
final String dbURL = JDBC_MEM_URL_PREFIX + name + flags ;
// hold a connection so h2 does not close the database
connection = getConnection(dbURL);
}
/**
* Start a remote/server database
* @throws Exception
*/
private void startRemoteDatabase() throws Exception
{
final File h2Dir = checkDataDir() ;
dbPath = new File(h2Dir, name);
// Start DB in new thread, or else it will block us
serverThread = new Thread("h2-" + name)
{
public void run()
{
try
{
log.debug( "Starting remote h2 db with port : " + port );
final String[] args = new String[] {
"-baseDir", dbPath.getAbsolutePath(),
"-tcpPort", String.valueOf(port),
"-tcpAllowOthers","" }; // need the extra empty string or a exception is thrown by H2
final Server server = Server.createTcpServer(args) ;
server.start() ;
setRemoteServer(server);
}
catch (Exception e)
{
log.error("Failed to start database", e);
}
}
};
serverThread.start();
if (delay > 0)
{
log.debug("Waiting for Database initialisation: maximum " + delay + " milliseconds") ;
try
{
serverThread.join(delay) ;
}
catch (final InterruptedException ie)
{
Thread.currentThread().interrupt() ;
}
if (serverThread.isAlive())
{
log.warn("Database initialisation is still active") ;
}
else
{
log.debug("Database initialisation completed") ;
}
}
}
/**
* Stop the standalone (in process) database.
*/
private void stopStandaloneDatabase() throws Exception
{
try
{
final Statement stmt = connection.createStatement() ;
stmt.execute("shutdown") ;
}
finally
{
connection = null;
}
log.info("Database standalone closed clean");
}
/**
* Stop the in memory database.
*/
private void stopInMemoryDatabase() throws Exception
{
try
{
connection.close() ;
}
finally
{
connection = null;
}
log.info("Database in memory closed clean");
}
/**
* Stop the remote database.
*/
private void stopRemoteDatabase() throws SQLException
{
final Server server = getRemoteServer() ;
if (server != null)
{
server.stop() ;
}
}
/**
* Set the remote server instance.
* @param remoteServer The remote server instance.
*/
private synchronized void setRemoteServer(final Server remoteServer)
{
this.remoteServer = remoteServer ;
}
/**
* Get the remote server instance.
* @return The remote server instance.
*/
private synchronized Server getRemoteServer()
{
return remoteServer ;
}
/**
* Get the connection.
*
* @param dbURL jdbc url.
* @return the connection, allocate one if needed.
* @throws Exception
*/
private synchronized Connection getConnection(String dbURL) throws Exception
{
if (connection == null)
{
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class.forName(JDBC_DRIVER_CLASS, true, cl).newInstance();
connection = DriverManager.getConnection(dbURL, user, password);
}
return connection;
}
/**
* Check the existence of the h2 data directory.
* @return The h2 data directory.
* @throws IOException For errors checking/creating the h2 data directory.
*/
private File checkDataDir() throws IOException
{
// Get the server data directory
final File dataDir = getDataDir();
// Get DB directory
final File h2Dir = new File(dataDir, H2_DATA_DIR);
if (!h2Dir.exists())
{
h2Dir.mkdirs();
}
else if (!h2Dir.isDirectory())
{
throw new IOException("Failed to create directory: " + h2Dir);
}
return h2Dir ;
}
File getDataDir()
{
if (datadir == null)
{
//final ServerConfigImpl serverConfig = (ServerConfigImpl) MBeanProxyExt.create(ServerConfigImplMBean.class,ServerConfigImplMBean.OBJECT_NAME);
//return serverConfig.getServerDataDir();
org.jboss.soa.dsp.server.ServerConfig serverConfig=
org.jboss.soa.bpel.runtime.JBossDSPFactory.getServerConfig();
return(serverConfig.getServerDeployDir());
}
return new File(datadir);
}
public void setDataDir(String datadir)
{
this.datadir = datadir;
}
}