/*
* StreamCruncher: Copyright (c) 2006-2008, Ashwin Jayaprakash. All Rights Reserved.
* Contact: ashwin {dot} jayaprakash {at} gmail {dot} com
* Web: http://www.StreamCruncher.com
*
* This file is part of StreamCruncher.
*
* StreamCruncher 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 3 of the License, or
* (at your option) any later version.
*
* StreamCruncher 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 StreamCruncher. If not, see <http://www.gnu.org/licenses/>.
*/
package streamcruncher.innards.db;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.dbcp.BasicDataSource;
import streamcruncher.api.DBName;
import streamcruncher.api.artifact.IndexSpec;
import streamcruncher.api.artifact.MiscSpec;
import streamcruncher.api.artifact.RowSpec;
import streamcruncher.api.artifact.TableSpec;
import streamcruncher.boot.Component;
import streamcruncher.boot.ConfigKeys;
import streamcruncher.boot.Registry;
import streamcruncher.innards.core.partition.aggregate.AbstractAggregatedColumnDDLHelper;
import streamcruncher.innards.impl.query.DDLHelper;
import streamcruncher.innards.query.Parser;
import streamcruncher.innards.util.Helper;
import streamcruncher.util.AtomicX;
import streamcruncher.util.LoggerManager;
import streamcruncher.util.sysevent.SystemEvent;
import streamcruncher.util.sysevent.SystemEventBus;
import streamcruncher.util.sysevent.SystemEvent.Priority;
/*
* Author: Ashwin Jayaprakash Date: Jan 2, 2006 Time: 9:40:59 AM
*/
public abstract class DatabaseInterface implements Component {
protected Properties properties;
protected BasicDataSource dataSource;
protected String schema;
protected boolean preservesArtifactsOnShutdown;
protected boolean privateVolatileInstance;
/**
* Auto-commit is <code>false</code>.
*/
protected Connection sentinelConnection;
protected Timer sentinelConnectionKeeper;
// ---------------------
/**
* {@inheritDoc} <code>params</code> requires the first parameter to be a
* {@link java.util.Properties} object loaded with the necessary Database
* properties.
*/
public void start(Object... params) throws Exception {
this.properties = (Properties) params[0];
String driver = properties.getProperty(ConfigKeys.DB.DRIVER_CLASS_NAME);
String url = properties.getProperty(ConfigKeys.DB.DRIVER_URL);
String user = properties.getProperty(ConfigKeys.DB.USER);
String password = properties.getProperty(ConfigKeys.DB.PASSWORD);
Class.forName(driver);
// ----------------------
schema = properties.getProperty(ConfigKeys.DB.SCHEMA);
if (schema != null && schema.length() == 0) {
schema = null;
}
String preservesArtifactsStr = properties
.getProperty(ConfigKeys.DB.PRESERVES_ARTIFACTS_ON_SHUTDOWN);
preservesArtifactsOnShutdown = Boolean.parseBoolean(preservesArtifactsStr);
String maxPoolSizeStr = properties.getProperty(ConfigKeys.DB.CONNECTION_POOL_MAX_SIZE);
int maxPoolSize = Integer.parseInt(maxPoolSizeStr);
String privateVolatileInstanceStr = properties
.getProperty(ConfigKeys.DB.PRIVATE_VOLATILE_INSTANCE);
privateVolatileInstance = Boolean.parseBoolean(privateVolatileInstanceStr);
dataSource = new BasicDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setMaxActive(maxPoolSize);
dataSource.setMaxIdle(maxPoolSize);
dataSource.setTestOnBorrow(false);
dataSource.setTestOnReturn(false);
dataSource.setTimeBetweenEvictionRunsMillis(2 * 60 * 1000);
dataSource.setMinEvictableIdleTimeMillis(30 * 1000);
dataSource.setAccessToUnderlyingConnectionAllowed(true);
dataSource.setPoolPreparedStatements(true);
setupLastStandingConnection();
// ----------------------
Logger logger = Registry.getImplFor(LoggerManager.class).getLogger(
DatabaseInterface.class.getName());
logger.log(Level.INFO, "Started");
}
private void setupLastStandingConnection() throws SQLException {
if (privateVolatileInstance == true) {
sentinelConnection = createConnection();
sentinelConnection.setAutoCommit(false);
sentinelConnectionKeeper = new Timer("SentinelConnectionKeeper", true);
/*
* Keep checking periodically and ensure that the sentinelConnection
* does not timeout.
*/
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
boolean isClosed = false;
Throwable error = null;
try {
isClosed = DatabaseInterface.this.sentinelConnection.isClosed();
}
catch (SQLException e1) {
error = e1;
}
if (error != null || isClosed) {
Logger logger = Registry.getImplFor(LoggerManager.class).getLogger(
DatabaseInterface.class.getName());
String msg = "The Private/Volatile Database instance may be at"
+ " risk, because the sentinel Connection has been lost.";
if (error == null) {
logger.log(Level.SEVERE, msg);
}
else {
logger.log(Level.SEVERE, msg, error);
}
SystemEventBus bus = Registry.getImplFor(SystemEventBus.class);
SystemEvent event = new SystemEvent(DatabaseInterface.class.getName(),
"Sentinel Connection Lost", error, Priority.SEVERE);
bus.submit(event);
// -----------
// Try and create a new one.
try {
Timer oldTimer = DatabaseInterface.this.sentinelConnectionKeeper;
DatabaseInterface.this.setupLastStandingConnection();
// Cancel this old timer.
oldTimer.cancel();
}
catch (SQLException e) {
logger.log(Level.SEVERE, "An error occurred while the sentinel"
+ " Connection was being re-created.", e);
event = new SystemEvent(DatabaseInterface.class.getName(),
"Sentinel Connection Re-creation Error", null, Priority.SEVERE);
bus.submit(event);
return;
}
}
// ------------
try {
DatabaseInterface.this.sentinelConnection.commit();
}
catch (SQLException e) {
Logger logger = Registry.getImplFor(LoggerManager.class).getLogger(
DatabaseInterface.class.getName());
logger.log(Level.SEVERE, "An error occurred while the sentinel"
+ " Connection was pinging the Database.", e);
SystemEventBus bus = Registry.getImplFor(SystemEventBus.class);
SystemEvent event = new SystemEvent(DatabaseInterface.class.getName(),
"Sentinel Connection Ping Error", null, Priority.SEVERE);
bus.submit(event);
}
}
};
sentinelConnectionKeeper.scheduleAtFixedRate(timerTask, 10 * 1000, 30 * 1000);
}
}
public void stop() throws Exception {
if (privateVolatileInstance == true) {
sentinelConnectionKeeper.cancel();
/*
* Hold on to this until the very end. Otherwise, the In-mem DBs
* will shutdown.
*/
Helper.closeConnection(sentinelConnection);
sentinelConnection = null;
}
dataSource.close();
dataSource = null;
// ---------------------
Logger logger = Registry.getImplFor(LoggerManager.class).getLogger(
DatabaseInterface.class.getName());
logger.log(Level.INFO, "Stopped");
}
// ---------------------
public Connection createConnection() throws SQLException {
return dataSource.getConnection();
}
// ---------------------
public abstract Class<? extends Parser> getParser();
public abstract DBName getDBName();
public String getSchema() {
return schema;
}
public abstract AbstractAggregatedColumnDDLHelper getAggregatedColumnDDLHelper();
public abstract DDLHelper getDDLHelper();
public TableSpec createUnpartitionedTableSpec(String schema, String name, RowSpec rowSpec,
IndexSpec[] indexSpecs, MiscSpec[] otherClauses) {
return new TableSpec(schema, name, rowSpec, indexSpecs, otherClauses);
}
public IndexSpec createIndexSpec(String schema, String name, String tableName, boolean unique,
String columnName, boolean ascending) {
return new IndexSpec(schema, name, tableName, unique, columnName, ascending);
}
public IndexSpec createIndexSpec(String schema, String name, String tableName, boolean unique,
String[] columnNames, boolean[] ascending) {
return new IndexSpec(schema, name, tableName, unique, columnNames, ascending);
}
public boolean dbPreservesArtifactsOnShutdown() {
return preservesArtifactsOnShutdown;
}
public AtomicX createRowIdGenerator() {
return new AtomicX(new AtomicLong(Constants.DEFAULT_MONOTONIC_ID_VALUE));
}
public ResultSet wrapResultSet(ResultSet resultSet) throws SQLException {
return resultSet;
}
}