/*
* $Header: /home/cvs/jakarta-slide/src/stores/org/apache/slide/store/impl/rdbms/JDBCStore.java,v 1.18.2.1 2004/02/05 16:07:52 mholz Exp $
* $Revision: 1.18.2.1 $
* $Date: 2004/02/05 16:07:52 $
*
* ====================================================================
*
* Copyright 1999-2002 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.slide.store.impl.rdbms;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Hashtable;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDriver;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.commons.pool.impl.StackKeyedObjectPoolFactory;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceInitializationFailedException;
import org.apache.slide.common.ServiceParameterErrorException;
import org.apache.slide.common.ServiceParameterMissingException;
import org.apache.slide.store.ContentStore;
import org.apache.slide.store.LockStore;
import org.apache.slide.store.NodeStore;
import org.apache.slide.store.RevisionDescriptorStore;
import org.apache.slide.store.RevisionDescriptorsStore;
import org.apache.slide.store.SecurityStore;
import org.apache.slide.util.logger.Logger;
/**
* Store implementation that is able to store all information (like structure,
* locks and content) in a JDBC-aware relational database system. As this
* implementation only uses a single connection to the database, it does not
* work properly in production environments that require simultaneous access by
* multiple clients.
*
* @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
* @author Dirk Verbeeck
* @author <a href="mailto:cmlenz@apache.org">Christopher Lenz</a>
* @author <a href="christophe.lombart@sword-technologies.com">Christophe Lombart</a>
* @author <a href="mailto:ozeigermann@c1-fse.de">Oliver Zeigermann</a>
* @version $Revision: 1.18.2.1 $
*/
public class JDBCStore
extends AbstractRDBMSStore
implements LockStore, NodeStore, RevisionDescriptorsStore, RevisionDescriptorStore, SecurityStore, ContentStore {
public static final String DBCP_URL = "jdbc:apache:commons:dbcp";
public static final String DBCP_POOL_NAME = "dbcpPool";
public static final String TRANSACTION_NONE = "NONE";
public static final String TRANSACTION_READ_UNCOMMITTED = "READ_UNCOMMITTED";
public static final String TRANSACTION_READ_COMMITTED = "READ_COMMITTED";
public static final String TRANSACTION_REPEATABLE_READ = "REPEATABLE_READ";
public static final String TRANSACTION_SERIALIZABLE = "SERIALIZABLE";
public static final int DEFAUT_ISOLATION_LEVEL = Connection.TRANSACTION_READ_COMMITTED;
protected static String isolationLevelToString(int isolationLevel) {
String levelString;
switch (isolationLevel) {
case Connection.TRANSACTION_NONE :
levelString = TRANSACTION_NONE;
break;
case Connection.TRANSACTION_READ_UNCOMMITTED :
levelString = TRANSACTION_READ_UNCOMMITTED;
break;
case Connection.TRANSACTION_READ_COMMITTED :
levelString = TRANSACTION_READ_COMMITTED;
break;
case Connection.TRANSACTION_REPEATABLE_READ :
levelString = TRANSACTION_REPEATABLE_READ;
break;
case Connection.TRANSACTION_SERIALIZABLE :
levelString = TRANSACTION_SERIALIZABLE;
break;
default :
levelString = "UNKNOWN";
break;
}
return levelString;
}
protected static int stringToIsolationLevelToString(String levelString) {
if (TRANSACTION_NONE.equals(levelString)) {
return Connection.TRANSACTION_NONE;
} else if (TRANSACTION_READ_UNCOMMITTED.equals(levelString)) {
return Connection.TRANSACTION_READ_UNCOMMITTED;
} else if (TRANSACTION_READ_COMMITTED.equals(levelString)) {
return Connection.TRANSACTION_READ_COMMITTED;
} else if (TRANSACTION_REPEATABLE_READ.equals(levelString)) {
return Connection.TRANSACTION_REPEATABLE_READ;
} else if (TRANSACTION_SERIALIZABLE.equals(levelString)) {
return Connection.TRANSACTION_SERIALIZABLE;
} else {
return -1;
}
}
// ----------------------------------------------------- Instance Variables
/**
* Driver class name.
*/
protected String driver;
/**
* Connection URL.
*/
protected String url;
/**
* User name.
*/
protected String user = "";
/**
* Password.
*/
protected String password = "";
protected boolean useDbcpPooling = false;
protected int maxPooledConnections = -1;
protected int isolationLevel = DEFAUT_ISOLATION_LEVEL;
// -------------------------------------------------------- Service Methods
/**
* Initializes the data source with a set of parameters.
*
* @param parameters a Hashtable containing the parameters' name and
* associated value
* @exception ServiceParameterErrorException a service parameter holds an
* invalid value
* @exception ServiceParameterMissingException a required parameter is
* missing
*/
public void setParameters(Hashtable parameters)
throws ServiceParameterErrorException, ServiceParameterMissingException {
String value;
// Driver classname
value = (String) parameters.get("driver");
if (value == null) {
throw new ServiceParameterMissingException(this, "driver");
} else {
driver = value;
}
// Connection url
value = (String) parameters.get("url");
if (value == null) {
throw new ServiceParameterMissingException(this, "url");
} else {
url = value;
}
// User name
value = (String) parameters.get("user");
if (value != null) {
user = value;
}
// Password
value = (String) parameters.get("password");
if (value != null) {
password = value;
}
value = (String) parameters.get("isolation");
if (value != null) {
isolationLevel = stringToIsolationLevelToString(value);
if (isolationLevel == -1) {
getLogger().log(
"Could not set isolation level '"
+ value
+ "', allowed levels are "
+ TRANSACTION_NONE
+ ", "
+ TRANSACTION_READ_UNCOMMITTED
+ ", "
+ TRANSACTION_READ_COMMITTED
+ ", "
+ TRANSACTION_REPEATABLE_READ
+ ", "
+ TRANSACTION_SERIALIZABLE,
LOG_CHANNEL,
Logger.WARNING);
isolationLevel = DEFAUT_ISOLATION_LEVEL;
}
}
value = (String) parameters.get("dbcpPooling");
if (value != null) {
useDbcpPooling = "true".equals(value);
}
if (useDbcpPooling) {
value = (String) parameters.get("maxPooledConnections");
if (value != null) {
try {
maxPooledConnections = Integer.parseInt(value);
} catch (NumberFormatException nfe) {
getLogger().log(
"Could not set maximum pooled connections, parameter must be integer",
LOG_CHANNEL,
Logger.WARNING);
}
}
}
super.setParameters(parameters);
}
/**
* Initializes driver.
* <p/>
* Occurs in four steps :
* <li>Driver class is loaded</li>
* <li>Driver is instantiated</li>
* <li>Driver registration in the driver manager</li>
* <li>Creation of the basic tables, if they didn't exist before</li>
*
* @exception ServiceInitializationFailedException Throws an exception
* if the data source has already been initialized before
*/
public synchronized void initialize(NamespaceAccessToken token) throws ServiceInitializationFailedException {
// XXX might be done already in setParameter
if (!alreadyInitialized) {
try {
// Loading and registering driver
getLogger().log("Loading and registering driver '" + driver + "'", LOG_CHANNEL, Logger.INFO);
Class driverClass = Class.forName(driver);
Driver driverInstance = (Driver) driverClass.newInstance();
String levelString = isolationLevelToString(isolationLevel);
getLogger().log("Setting isolation level '" + levelString + "'", LOG_CHANNEL, Logger.INFO);
// use DBCP pooling if enabled
if (useDbcpPooling) {
getLogger().log("Using DBCP pooling", LOG_CHANNEL, Logger.INFO);
GenericObjectPool connectionPool = new GenericObjectPool(null);
if (maxPooledConnections != -1) {
connectionPool.setMaxActive(maxPooledConnections);
}
getLogger().log(
"Number of connections set to " + connectionPool.getMaxActive(),
LOG_CHANNEL,
Logger.INFO);
DriverManagerConnectionFactory connectionFactory =
new DriverManagerConnectionFactory(url, user, password);
new PoolableConnectionFactory(
connectionFactory,
connectionPool,
// TODO switching on pooling of prepared statements causes problems with closing of connections
// switched off for now
// new StackKeyedObjectPoolFactory(),
null,
null,
false,
false,
isolationLevel);
PoolingDriver driver = new PoolingDriver();
driver.registerPool(DBCP_POOL_NAME, connectionPool);
// already done when loding PoolingDriver class
// DriverManager.registerDriver(driver);
} else {
DriverManager.registerDriver(driverInstance);
getLogger().log("Not using DBCP pooling", LOG_CHANNEL, Logger.WARNING);
}
} catch (Exception e) {
getLogger().log(
"Loading and registering driver '" + driver + "' failed (" + e.getMessage() + ")",
LOG_CHANNEL,
Logger.ERROR);
throw new ServiceInitializationFailedException(this, e);
} finally {
alreadyInitialized = true;
}
}
}
protected Connection getNewConnection() throws SQLException {
Connection connection;
if (useDbcpPooling) {
try {
connection = DriverManager.getConnection(DBCP_URL + ":" + DBCP_POOL_NAME);
} catch (SQLException e) {
getLogger().log("Could not create connection. Reason: " + e, LOG_CHANNEL, Logger.EMERGENCY);
throw e;
}
} else {
try {
connection = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
getLogger().log("Could not create connection. Reason: " + e, LOG_CHANNEL, Logger.EMERGENCY);
throw e;
}
try {
if (connection.getTransactionIsolation() != isolationLevel) {
connection.setTransactionIsolation(isolationLevel);
}
} catch (SQLException e) {
getLogger().log(
"Could not set isolation level '" + isolationLevelToString(isolationLevel) + "'. Reason: " + e,
LOG_CHANNEL,
Logger.WARNING);
}
if (connection.getAutoCommit()) {
connection.setAutoCommit(false);
}
}
return connection;
}
}