/**
* Copyright (C) 2001-2004 France Telecom R&D
*
* This library 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 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.objectweb.speedo.mapper.rdb;
import org.objectweb.jorm.api.PException;
import org.objectweb.jorm.api.PExceptionIO;
import org.objectweb.jorm.api.JormConfigurator;
import org.objectweb.jorm.mapper.rdb.lib.MapperJDBC;
import org.objectweb.jorm.mapper.rdb.lib.ConnectionSpecJDBC;
import org.objectweb.fractal.api.control.LifeCycleController;
import org.objectweb.fractal.api.control.BindingController;
import org.objectweb.util.monolog.api.LoggerFactory;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.wrapper.p6spy.P6SpyLogger;
import org.objectweb.speedo.api.ExceptionHelper;
import org.objectweb.speedo.mapper.rdb.JDBCConnectionHolder;
import org.objectweb.medor.eval.prefetch.lib.PrefetchCacheImpl;
import org.objectweb.perseus.pool.api.PoolMatchFactory;
import org.objectweb.perseus.pool.api.Pool;
import org.objectweb.perseus.pool.api.PoolException;
import org.objectweb.perseus.persistence.api.ConnectionHolderFactory;
import org.objectweb.perseus.persistence.api.ConnectionHolder;
import org.objectweb.perseus.persistence.api.PersistenceException;
import java.sql.DriverManager;
import java.sql.Driver;
import java.util.ArrayList;
/**
* A JDBC mapper component able to pool the JDBC connection in non managed
* environnement.
*
* @author S.Chassande-Barrioz
*/
public class JDBCMapper
extends MapperJDBC
implements BindingController, LifeCycleController,
JDBCMapperAttributes, PoolMatchFactory, ConnectionHolderFactory {
public final static String POOL_BINDING = "pool";
/**
* The pool of JDBC connection.
*/
protected Pool connectionPool;
/**
* The database url. This field is null when a Datasource is used.
*/
private String url;
/**
* The user name to access the database. This field is null when a
* Datasource is used.
*/
private String userName;
/**
* The user passwor to access the database. This field is null when a
* Datasource is used.
*/
private String password;
/**
* The class name of the JDBC driver. This field is null when a
* Datasource is used.
*/
private String driverCN = null;
/**
* Indicates if the component is started;
*/
private boolean started = false;
/**
* Indicates if JDBC connection must be pool or not.
*/
private boolean poolConnection = false;
/**
* Contains the connection allocated for a particular user, in a pooled mode
*/
private ArrayList unpooledConnection;
private boolean checkConnectivityAtStartup = true;
public JDBCMapper() throws PException {
}
// IMPLEMENTATION OF THE ConnectionHolderFactory INTERFACE //
//---------------------------------------------------------//
public ConnectionHolder createConnectionHolder() throws PersistenceException {
return new JDBCConnectionHolder(this, logger);
}
// IMPLEMENTATION OF THE PoolMatchFactory INTERFACE //
//--------------------------------------------------//
/**
* <b>createResource</b> creates a new PoolResource.
* @param hints The "properties" that the created PoolResource should
* conform to.
* @return The created PoolResource.
*/
public Object createResource(Object hints) throws PoolException {
if (!started) {
startFc();
}
Object connection = null;
try {
if (hints == null) {
connection = super.getConnection();
} else {
connection = super.getConnection(hints);
}
} catch (PException e) {
throw new PoolException("Error during the allocation a new JDBC connection", e);
}
if (logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG,
"New JDBC connection allocated for the pool:" + connection);
}
return connection;
}
/**
* <b>matchResource</b> tests if a given resource of a Pool matches with
* the hints passed with the Pool getResource method.
* @param pr The PoolResource to test its matching with some
* "properties" specified by hints.
* @param hints The "properties" that the PoolResource specified by pr
* should match.
* @return <b>true</b> if the pr PoolResource matches the hints
* "properties".
*/
public boolean matchResource(Object pr, Object hints) {
return true;
}
public void destroyResource(Object resource) {
if (logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG,
"Destroy the JDBC connection of the pool:" + resource);
}
try {
super.closeConnection(resource);
} catch (PException e) {
if (logger != null) {
logger.log(BasicLevel.ERROR,
"ERROR during the closing of a connection of the pool:"
+ resource, e);
}
}
}
// IMPLEMENTATION OF THE JDBCMapperAttributes INTERFACE //
//------------------------------------------------------//
public String getDriverClassName() {
return driverCN;
}
public void setDriverClassName(String dcn) {
driverCN = dcn;
}
public String getURL() {
return url;
}
public void setURL(String url) {
this.url = url;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void setPoolConnection(boolean pc) {
poolConnection = pc;
if (poolConnection) {
unpooledConnection = new ArrayList(5);
}
}
public boolean getPoolConnection() {
return poolConnection;
}
// IMPLEMENTATION OF THE UserBindingController INTERFACE //
//-------------------------------------------------------//
public String[] listFc() {
return new String[] {
POOL_BINDING
};
}
public Object lookupFc(String s) {
if (POOL_BINDING.equals(s)) {
return connectionPool;
} else {
return null;
}
}
public void bindFc(String s, Object o) {
if ("logger".equals(s)) {
logger = (Logger) o;
if (P6SpyLogger.logger == null) {
P6SpyLogger.logger = logger;
}
} else if ("monolog-factory".equals(s)) {
Logger l = logger;
setLoggerFactory((LoggerFactory) o);
if (l != null) {
logger = l;
}
P6SpyLogger.logger = getLoggerFactory()
.getLogger(logger.getName() + ".sql");
} else if (POOL_BINDING.equals(s)) {
connectionPool = (Pool) o;
}
}
public void unbindFc(String s) {
if (POOL_BINDING.equals(s)) {
connectionPool = null;
}
}
// IMPLEMENTATION OF THE LifeCycleController INTERFACE //
//-----------------------------------------------------//
public String getFcState() {
return started ? STARTED : STOPPED;
}
public void startFc() {
if (!started) {
if (logger == null) {
String msg = "No logger assigned on the component before the start.";
System.err.println(msg);
throw new RuntimeException(msg);
}
started = true;
P6SpyLogger.level = BasicLevel.DEBUG;
Object cf = getConnectionFactory();
if (cf == null) {
try {
DriverManager.registerDriver((Driver)
Class.forName(driverCN).newInstance());
setConnectionFactory(
new ConnectionSpecJDBC(url, driverCN, userName, password));
} catch (Exception e) {
logger.log(BasicLevel.ERROR,
"Impossible to configure the jdbc access: ", e);
throw new RuntimeException(
"Impossible to configure the jdbc access: "
+ ExceptionHelper.getNested(e).getMessage());
}
}
if (checkConnectivityAtStartup) {
Object o = null;
try {
logger.log(BasicLevel.DEBUG, "try to fetch a connection");
o = getConnection();
} catch (Exception e) {
Exception ie = ExceptionHelper.getNested(e);
logger.log(BasicLevel.ERROR,
"Impossible to fetch a connection", ie);
throw new RuntimeException(
"Impossible to fetch a connection: "
+ ie.getMessage());
} finally {
if (o != null) {
try {
closeConnection(o);
} catch (PException e) {
}
}
}
}
try {
JormConfigurator jc = getJormConfigurator();
jc.setLoggerFactory(getLoggerFactory());
PrefetchCacheImpl pc = new PrefetchCacheImpl(
getLoggerFactory().getLogger(
"org.objectweb.speedo.rt.query.prefetch"));
setPrefetchCache(pc);
start();
} catch (PException e) {
throw new RuntimeException(
"Impossible to configure the mapper: "
+ ExceptionHelper.getNested(e).getMessage());
}
}
}
public void stopFc() {
started = false;
}
// IMPLEMENTATION OF THE PMapper INTERFACE //
//-----------------------------------------//
/**
* The pool is used to fetch a connection.
*/
public Object getConnection() throws PException {
if (poolConnection) {
try {
Object connection = connectionPool.getResource(null);
if (logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "Get a JDBC connection from the pool: " + connection);
}
return connection;
} catch (Exception e) {
throw new PExceptionIO(ExceptionHelper.getNested(e),
"Impossible to fetch a jdbc connection on driver");
}
} else {
Object connection = super.getConnection();
if (logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "JDBC connection allocated: " + connection);
}
return connection;
}
}
public Object getConnection(Object connectionContext, Object user) throws PException {
if (poolConnection) {
if (connectionContext == null) {
try {
Object connection = connectionPool.getResource(null, user);
if (logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "Get a JDBC connection from the pool: " + connection);
}
return connection;
} catch (Exception e) {
throw new PExceptionIO(ExceptionHelper.getNested(e),
"Impossible to fetch a jdbc connection on driver");
}
} else {
Object connection = super.getConnection(connectionContext, user);
synchronized(unpooledConnection) {
unpooledConnection.add(connection);
}
if (logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG,
"JDBC connection allocated with context, context= "
+ connectionContext + ", connection=" + connection);
}
return connection;
}
} else {
Object connection = super.getConnection(connectionContext, user);
if (logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "JDBC connection allocated: " + connection);
}
return connection;
}
}
/**
* The connection is release into the pool
* @param conn
* @throws org.objectweb.jorm.api.PException
*/
public void closeConnection(Object conn) throws PException {
if (conn == null) {
return;
}
if (poolConnection) {
if (unpooledConnection != null) {
synchronized(unpooledConnection) {
if (unpooledConnection.remove(unpooledConnection)) {
if (logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "Closing the JDBC connection (context): " + conn);
}
return;
} //else the connection has not been found
}
}
try {
connectionPool.releaseResource(conn);
} catch (Exception e) {
throw new PExceptionIO(ExceptionHelper.getNested(e),
"Impossible to release a jdbc connection");
}
} else {
if (logger != null && logger.isLoggable(BasicLevel.DEBUG)) {
logger.log(BasicLevel.DEBUG, "Closing the JDBC connection: " + conn);
}
super.closeConnection(conn);
}
}
public boolean getCheckConnectivityAtStartup() {
return this.checkConnectivityAtStartup;
}
public void setCheckConnectivityAtStartup(boolean b) {
checkConnectivityAtStartup = b;
}
}