/* * Copyright 2001-2008 Geert Bevin (gbevin[remove] at uwyn dot com) * Licensed under the Apache License, Version 2.0 (the "License") * $Id: Datasource.java 3918 2008-04-14 17:35:35Z gbevin $ */ package com.uwyn.rife.database; import com.uwyn.rife.database.exceptions.*; import com.uwyn.rife.database.capabilities.CapabilitiesCompensator; import com.uwyn.rife.database.types.SqlConversion; import com.uwyn.rife.tools.ExceptionUtils; import com.uwyn.rife.tools.StringUtils; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.HashMap; import java.util.logging.Logger; import javax.sql.DataSource; /** * Contains all the information required to connect to a database and * centralizes the creation of connections to a database. These connections can * optionally be pooled. * <p> * The initial connection will only be made and the pool will only be * initialized when a connection is obtained for the first time. The * instantiation only stores the connection parameters. * <p> * A <code>Datasource</code> also defines the type of database that is used for * all database-independent logic such as sql to java and java to sql type * mappings, query builders, database-based authentication, database-based * scheduling, ... The key that identifies a supported type is the class name of * the jdbc driver. * <p> * A <code>Datasource</code> instance can be created through it's constructor, * but it's recommended to work with a <code>Datasources</code> collection * that is created and populated through XML. This can easily be achieved by * using a <code>ParticipantDatasources</code> which participates in the * application-wide repository. * <p> * Once a connection has been obtained from a pooled datasource, modifying its * connection parameters is not possible anymore, a new instance has to be * created to set the parameters to different values. * * @author Geert Bevin (gbevin[remove] at uwyn dot com) * @version $Revision: 3918 $ * @see com.uwyn.rife.database.Datasources * @see com.uwyn.rife.database.Xml2Datasources * @see com.uwyn.rife.rep.Rep * @see com.uwyn.rife.rep.participants.ParticipantDatasources * @since 1.0 */ public class Datasource implements Cloneable { static HashMap<String, String> sDriverAliases = new HashMap<String, String>(); static HashMap<String, String> sDriverNames = new HashMap<String, String>(); static { sDriverAliases.put("org.gjt.mm.mysql.Driver", "com.mysql.jdbc.Driver"); sDriverAliases.put("in.co.daffodil.db.rmi.RmiDaffodilDBDriver", "in.co.daffodil.db.jdbc.DaffodilDBDriver"); sDriverAliases.put("oracle.jdbc.OracleDriver", "oracle.jdbc.driver.OracleDriver"); sDriverAliases.put("org.apache.derby.jdbc.ClientDriver", "org.apache.derby.jdbc.EmbeddedDriver"); sDriverNames.put("Apache Derby Embedded JDBC Driver", "org.apache.derby.jdbc.EmbeddedDriver"); sDriverNames.put("Apache Derby Network Client JDBC Driver", "org.apache.derby.jdbc.EmbeddedDriver"); sDriverNames.put("DaffodilDBDriver", "in.co.daffodil.db.jdbc.DaffodilDBDriver"); sDriverNames.put("H2 JDBC Driver", "org.h2.Driver"); sDriverNames.put("HSQL Database Engine Driver", "org.hsqldb.jdbcDriver"); sDriverNames.put("Jaybird JCA/JDBC driver", "org.firebirdsql.jdbc.FBDriver"); sDriverNames.put("Mckoi JDBC Driver", "com.mckoi.JDBCDriver"); sDriverNames.put("MySQL-AB JDBC Driver", "com.mysql.jdbc.Driver"); sDriverNames.put("Oracle JDBC driver", "oracle.jdbc.driver.OracleDriver"); sDriverNames.put("PostgreSQL Native Driver", "org.postgresql.Driver"); } private String mDriver = null; private String mUrl = null; private String mUser = null; private String mPassword = null; private SqlConversion mSqlConversion = null; private CapabilitiesCompensator mCapabilitiesCompensator = null; private ConnectionPool mConnectionPool = new ConnectionPool(); private DataSource mDataSource = null; /** * Instantiates a new <code>Datasource</code> object with no connection * information. The setters need to be used afterwards to provide each * required parameter before the <code>Datasource</code> can be used. * * @see #setDriver(String) * @see #setUrl(String) * @see #setUser(String) * @see #setPassword(String) * @see #setPoolsize(int) * @see #setDataSource(DataSource) * * @since 1.0 */ public Datasource() { } /** * Instantiates a new <code>Datasource</code> object with all the * connection parameters that are required. * * @param driver the fully-qualified classname of the jdbc driver that will * be used to connect to the database * @param url the connection url which identifies the database to which the * connection will be made, this is entirely driver-dependent * @param user the user that will be used to connect to the database * @param password the password that will be used to connect to the database * @param poolsize the size of the connection pool, <code>0</code> means * that the connections will not be pooled * * @since 1.0 */ public Datasource(String driver, String url, String user, String password, int poolsize) { setDriver(driver); setUrl(url); setUser(user); setPassword(password); setPoolsize(poolsize); assert mDriver != null; assert mDriver.length() > 0; assert mUrl != null; assert mUrl.length() > 0; } /** * Instantiates a new <code>Datasource</code> object from a standard * <code>javax.sql.DataSource</code>. * <p> * The driver will be detected from the connection that is provided by this * <code>DataSource</code>. If the driver couldn't be detected, an exception * will be thrown upon connect. * * @param dataSource the standard datasource that will be used to obtain the * connection * @param poolsize the size of the connection pool, <code>0</code> means * that the connections will not be pooled * * @since 1.3 */ public Datasource(DataSource dataSource, int poolsize) { setDataSource(dataSource); setPoolsize(poolsize); assert dataSource != null; } /** * Instantiates a new <code>Datasource</code> object from a standard * <code>javax.sql.DataSource</code>. * * @param dataSource the standard datasource that will be used to obtain the * connection * @param driver the fully-qualified classname of the jdbc driver that will * be used to provide an identifier for the database abstraction functionalities, * <code>null</code> will let RIFE try to figure it out by itself * @param user the user that will be used to connect to the database * @param password the password that will be used to connect to the database * @param poolsize the size of the connection pool, <code>0</code> means * that the connections will not be pooled * * @since 1.3 */ public Datasource(DataSource dataSource, String driver, String user, String password, int poolsize) { setDataSource(dataSource); mDriver = driver; mSqlConversion = null; setUser(user); setPassword(password); setPoolsize(poolsize); assert dataSource != null; } /** * Creates a new connection by using all the parameters that have been * defined in the <code>Datasource</code>. * * @return the newly created <code>DbConnection</code> instance * * @throws DatabaseException when an error occured during the creation of * the connection * * @since 1.0 */ DbConnection createConnection() throws DatabaseException { Connection connection = null; if (this.mDataSource != null) { // try to create a datasource connection if (null != mUser && null != mPassword) { try { connection = this.mDataSource.getConnection(mUser, mPassword); } catch (SQLException e) { throw new ConnectionOpenErrorException(null, mUser, mPassword, e); } } else { try { connection = this.mDataSource.getConnection(); } catch (SQLException e) { throw new ConnectionOpenErrorException(null, e); } } if (null == mDriver) { try { String driver_name = connection.getMetaData().getDriverName(); mDriver = sDriverNames.get(driver_name); if (null == mDriver) { throw new UnsupportedDriverNameException(driver_name); } } catch (SQLException e) { throw new DriverNameRetrievalErrorException(e); } } } else { // obtain the jdbc driver instance try { Class.forName(mDriver).newInstance(); } catch (InstantiationException e) { throw new DriverInstantiationErrorException(mDriver, e); } catch (ClassNotFoundException e) { throw new DriverInstantiationErrorException(mDriver, e); } catch (IllegalAccessException e) { throw new DriverInstantiationErrorException(mDriver, e); } // try to create a jdbc connection if (null != mUser && null != mPassword) { try { connection = DriverManager.getConnection(mUrl, mUser, mPassword); } catch (SQLException e) { throw new ConnectionOpenErrorException(mUrl, mUser, mPassword, e); } } else { try { connection = DriverManager.getConnection(mUrl); } catch (SQLException e) { throw new ConnectionOpenErrorException(mUrl, e); } } } // returns a new DbConnection instance with contains the new jdbc // connection and is linked to this datasource return new DbConnection(connection, this); } /** * Retrieves a free database connection. If no connection pool is used, a * new <code>DbConnection</code> will always be created, otherwise the first * available connection in the pool will be returned. * * @return a free <code>DbConnection</code> instance which can be used to * create an execute statements * * @throws DatabaseException when errors occured during the creation of a * new connection or during the obtainance of a connection from the pool * * @since 1.0 */ public DbConnection getConnection() throws DatabaseException { return mConnectionPool.getConnection(this); } /** * Retrieves the fully qualified class name of the jdbc driver that's used * by this <code>Datasource</code>. * * @return a <code>String</code> with the name of the jdbc driver; or * <p> * <code>null</code> if the driver hasn't been set * * @see #setDriver(String) * @see #getAliasedDriver() * * @since 1.0 */ public String getDriver() { // make sure that a JNDI connection has been made first, so that the database name can be looked up if (mDataSource != null && null == mDriver) { getConnection(); } return mDriver; } /** * Retrieves the fully qualified class name of the jdbc driver that's used * by this <code>Datasource</code>. Instead of straight retrieval of the * internal value, it looks for jdbc driver aliases and changes the driver * classname if it's not supported by RIFE, but its alias is. * * @return a <code>String</code> with the name of the jdbc driver; or * <p> * <code>null</code> if the driver hasn't been set * * @see #getDriver() * @see #setDriver(String) * * @since 1.0 */ public String getAliasedDriver() { String driver = getDriver(); if (null == driver) { return null; } String alias = sDriverAliases.get(driver); if (null == alias) { return driver; } return alias; } /** * Sets the jdbc driver that will be used to connect to the database. This * has to be a fully qualified class name and will be looked up through * reflection. It's not possible to change the driver after a connection * has been obtained from a pooled datasource. * <p> * If the class name can't be resolved, an exception is thrown during the * creation of the first connection. * * @param driver a <code>String</code> with the fully qualified class name * of the jdbc driver that will be used * * @see #getDriver() * * @since 1.0 */ public void setDriver(String driver) { if (null == driver) throw new IllegalArgumentException("driver can't be null."); if (0 == driver.length()) throw new IllegalArgumentException("driver can't be empty."); if (mConnectionPool.isInitialized()) throw new IllegalArgumentException("driver can't be changed after the connection pool has been set up."); mDriver = driver; mSqlConversion = null; } /** * Retrieves the standard datasource that is used by this RIFE datasource * to obtain a database connection. * * @return a standard <code>DataSource</code>; or * <p> * <code>null</code> if the standard datasource hasn't been set * * @see #setDataSource(DataSource) * * @since 1.3 */ public DataSource getDataSource() { return mDataSource; } /** * Sets the standard datasource that will be used to connect to the database. * * @param dataSource a standard <code>DataSource</code> that will be used * by this RIFE datasource to obtain a database connection. * * @see #getDataSource() * * @since 1.0 */ public void setDataSource(DataSource dataSource) { mDataSource = dataSource; } /** * Retrieves the connection url that's used by this <code>Datasource</code>. * * @return a <code>String</code> with the connection url; or * <p> * <code>null</code> if the url hasn't been set * * @see #setUrl(String) * * @since 1.0 */ public String getUrl() { return mUrl; } /** * Sets the connection url that will be used to connect to the database. * It's not possible to change the url after a connection has been obtained * from a pooled datasource. * * @param url a <code>String</code> with the connection url that will be * used * * @see #getUrl() * * @since 1.0 */ public void setUrl(String url) { if (null == url) throw new IllegalArgumentException("url can't be null."); if (0 == url.length()) throw new IllegalArgumentException("url can't be empty."); if (mConnectionPool.isInitialized()) throw new IllegalArgumentException("url can't be changed after the connection pool has been set up."); mUrl = url; } /** * Retrieves the user that's used by this <code>Datasource</code>. * * @return a <code>String>/code> with the user; or * <p> * <code>null</code> if the user hasn't been set * * @see #setUser(String) * * @since 1.0 */ public String getUser() { return mUser; } /** * Sets the user that will be used to connect to the database. * It's not possible to change the user after a connection has been obtained * from a pooled datasource. * * @param user a <code>String</code> with the user that will be used * * @see #getUser() * * @since 1.0 */ public void setUser(String user) { if (mConnectionPool.isInitialized()) throw new IllegalArgumentException("user can't be changed after the connection pool has been set up."); mUser = user; } /** * Retrieves the password that's used by this <code>Datasource</code>. * * @return a <code>String>/code> with the password; or * <p> * <code>null</code> if the password hasn't been set * * @see #setPassword(String) * * @since 1.0 */ public String getPassword() { return mPassword; } /** * Sets the password that will be used to connect to the database. * It's not possible to change the password after a connection has been * obtained from a pooled datasource. * * @param password a <code>String</code> with the password that will be used * * @see #getPassword() * * @since 1.0 */ public void setPassword(String password) { if (mConnectionPool.isInitialized()) throw new IllegalArgumentException("password can't be changed after the connection pool has been set up."); mPassword = password; } /** * Retrieves the size of the pool that's used by this * <code>Datasource</code>. * * @return a positive <code>int</code> with the size of the pool; or * <p> * <code>0</code> if no pool is being used * * @see #isPooled() * @see #setPoolsize(int) * * @since 1.0 */ public int getPoolsize() { return mConnectionPool.getPoolsize(); } /** * Indicates whether the <code>Datasource</code> uses a connection pool or * not * * @return <code>true</code> if a pool is being used by this * <code>Datasource</code>; or * <p> * <code>false</code> otherwise * * @see #getPoolsize() * @see #setPoolsize(int) * * @since 1.0 */ public boolean isPooled() { return getPoolsize() > 0; } /** * Sets the size of the connection pool that will be used to connect to the * database. * * @param poolsize a positive <code>int</code> with the size of the pool, * providing <code>0</code> will disable the use of a connection pool for * this <code>Datasource</code>. * * @see #getPoolsize() * @see #isPooled() * * @since 1.0 */ public void setPoolsize(int poolsize) { if (poolsize < 0) throw new IllegalArgumentException("poolsize can't be negative."); mConnectionPool.setPoolsize(poolsize); } /** * Retrieves the sql to java and java to sql type mapping logic that * corresponds to the provide driver class name. * * @return a <code>SqlConversion</code> instance that is able to perform * the required type conversions for the provided jdbc driver * * @throws UnsupportedJdbcDriverException when the provided jdbc isn't * supported * * @since 1.0 */ public SqlConversion getSqlConversion() throws UnsupportedJdbcDriverException { String driver = getDriver(); if (null == mSqlConversion && null != driver) { try { mSqlConversion = (SqlConversion)Class.forName("com.uwyn.rife.database.types.databasedrivers."+StringUtils.encodeClassname(getAliasedDriver())).newInstance(); } catch (InstantiationException e) { throw new UnsupportedJdbcDriverException(driver, e); } catch (IllegalAccessException e) { throw new UnsupportedJdbcDriverException(driver, e); } catch (ClassNotFoundException e) { throw new UnsupportedJdbcDriverException(driver, e); } } return mSqlConversion; } /** * Retrieves a <code>CapabilitiesCompensator</code> instance that is able to * compensate for certain missing database features * * @return the requested <code>CapabilitiesCompensator</code> instance * * @throws UnsupportedJdbcDriverException when the provided jdbc isn't * supported * * @since 1.0 */ public CapabilitiesCompensator getCapabilitiesCompensator() throws UnsupportedJdbcDriverException { String driver = getDriver(); if (null == mCapabilitiesCompensator && null != driver) { try { mCapabilitiesCompensator = (CapabilitiesCompensator)Class.forName("com.uwyn.rife.database.capabilities."+StringUtils.encodeClassname(getAliasedDriver())).newInstance(); } catch (InstantiationException e) { throw new UnsupportedJdbcDriverException(driver, e); } catch (IllegalAccessException e) { throw new UnsupportedJdbcDriverException(driver, e); } catch (ClassNotFoundException e) { throw new UnsupportedJdbcDriverException(driver, e); } } return mCapabilitiesCompensator; } /** * Returns a hash code value for the <code>Datasource</code>. This method is * supported for the benefit of hashtables such as those provided by * <code>java.util.Hashtable</code>. * * @return an <code>int</code> with the hash code value for this * <code>Datasource</code>. * * @see #equals(Object) * * @since 1.0 */ public int hashCode() { int dataSourceHash = mDataSource == null ? 1 : mDataSource.hashCode(); int driverHash = mDriver == null ? 1 : mDriver.hashCode (); int urlHash = mUrl == null ? 1 : mUrl.hashCode(); int userHash = mUser == null ? 1 : mUser.hashCode(); int passwordHash = mPassword == null ? 1 : mPassword.hashCode(); return dataSourceHash * driverHash * urlHash * userHash * passwordHash; } /** * Indicates whether some other object is "equal to" this one. Only the * real connection parameters will be taken into account. The size of the * pool is not used for the comparison. * * @param object the reference object with which to compare. * @return <code>true</code> if this object is the same as the object * argument; or * <p> * <code>false</code> otherwise * * @see #hashCode() * * @since 1.0 */ public boolean equals(Object object) { if (this == object) { return true; } if (null == object) { return false; } if (!(object instanceof Datasource)) { return false; } Datasource other_datasource = (Datasource)object; if (!other_datasource.getDriver().equals(getDriver())) { return false; } if (other_datasource.getUrl() != null || getUrl() != null) { if (null == other_datasource.getUrl() || null == getUrl()) { return false; } if (!other_datasource.getUrl().equals(getUrl())) { return false; } } if (other_datasource.getDataSource() != null || getDataSource() != null) { if (null == other_datasource.getDataSource() || null == getDataSource()) { return false; } if (!other_datasource.getDataSource().equals(getDataSource())) { return false; } } if (other_datasource.getUser() != null || getUser() != null) { if (null == other_datasource.getUser() || null == getUser()) { return false; } if (!other_datasource.getUser().equals(getUser())) { return false; } } if (other_datasource.getPassword() != null || getPassword() != null) { if (null == other_datasource.getPassword() || null == getPassword()) { return false; } if (!other_datasource.getPassword().equals(getPassword())) { return false; } } return true; } /** * Simply clones the instance with the default clone method. This creates a * shallow copy of all fields and the clone will in fact just be another * reference to the same underlying data. The independence of each cloned * instance is consciously not respected since they rely on resources * that can't be cloned. * * @since 1.0 */ public Datasource clone() { Datasource other = null; try { other = (Datasource)super.clone(); } catch (CloneNotSupportedException e) { // this should never happen Logger.getLogger("com.uwyn.rife.database").severe(ExceptionUtils.getExceptionStackTrace(e)); return null; } other.mSqlConversion = mSqlConversion; other.mConnectionPool = mConnectionPool; return other; } /** * Cleans up all connections that have been reserved by this datasource. * * @throws DatabaseException when an error occured during the cleanup * * @since 1.0 */ public void cleanup() throws DatabaseException { mConnectionPool.cleanup(); } /** * Retrieves the instance of the connection pool that is provided by this * dtaasource. * * @return the requested instance of <code>ConnectionPool</code> * */ public ConnectionPool getPool() { return mConnectionPool; } }