/** $Header: /home/cvs/commons/dbcp-1.0/ja/src/org/apache/commons/dbcp/BasicDataSource.java,v 1.1.1.1 2004/02/13 10:02:03 hioki Exp $
 * $Revision: 1.1.1.1 $
 * $Date: 2004/02/13 10:02:03 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package org.apache.commons.dbcp;

import java.io.PrintWriter;
import java.util.Properties;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.commons.dbcp.DriverConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;


/**
 * <p>
 * JavaBeans プロパティを介して設定される <code>javax.sql.DataSource</code> の基本的な実装です。
 * <em>commons-dbcp</em> パッケージと <em>commons-pool</em> パッケージと共にでなければ使用できないわけではありませんが、
 * 基本的な要求に対する "ワンストップ・ショッピング(要は上記パッケージに依存する機能)" ソリューションを提供しています。
 * {@primary Basic implementation of <code>javax.sql.DataSource</code> that is
 * configured via JavaBeans properties.  This is not the only way to
 * combine the <em>commons-dbcp</em> and <em>commons-pool</em> packages,
 * but provides a "one stop shopping" solution for basic requirements.}
 * </p>
 *
 * @author Glenn L. Nielsen
 * @author Craig R. McClanahan
 * @translator 日置 聡
 * @status firstdraft
 * @update 2003/09/13
 * @version $Revision: 1.1.1.1 $ $Date: 2004/02/13 10:02:03 $
 */

public class BasicDataSource implements DataSource {


    // ------------------------------------------------------------- Properties


    /**
     * このプールにて生成されるコネクションのデフォルトのオートコミットのステータス。
     * {@primary The default auto-commit state of connections created by this pool.}
     */
    protected boolean defaultAutoCommit = true;

    public boolean getDefaultAutoCommit() {
        return (this.defaultAutoCommit);
    }

    public void setDefaultAutoCommit(boolean defaultAutoCommit) {
        this.defaultAutoCommit = defaultAutoCommit;
    }


    /**
     * このプールにて生成されるコネクションのデフォルトの読み込み専用のステータス。
     * {@primary The default read-only state of connections created by this pool.}
     */
    protected boolean defaultReadOnly = false;

    public boolean getDefaultReadOnly() {
        return (this.defaultReadOnly);
    }

    public void setDefaultReadOnly(boolean defaultReadOnly) {
        this.defaultReadOnly = defaultReadOnly;
    }


    /**
     * 使用する JDBC ドライバの Java クラスの完全修飾名です。
     * {@primary The fully qualified Java class name of the JDBC driver to be used.}
     */
    protected String driverClassName = null;

    public String getDriverClassName() {
        return (this.driverClassName);
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }


    /**
     * このプールから1度に割り当てることのできるアクティブなコネクションの最大数です。
     * 0が設定された場合には制限を行いません。
     * {@primary The maximum number of active connections that can be allocated from
     * this pool at the same time, or zero for no limit.}
     */
    protected int maxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE;

    public int getMaxActive() {
        return (this.maxActive);
    }

    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
    }


    /**
     * このプール内で開放されずにアイドル状態でいられるアクティブなコネクションの最大数です。
     * 0が設定された場合には制限を行いません。
     * {@primary The maximum number of active connections that can remain idle in the
     * pool, without extra ones being released, or zero for no limit.}
     */
    protected int maxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;;

    public int getMaxIdle() {
        return (this.maxIdle);
    }

    public void setMaxIdle(int maxIdle) {
        this.maxIdle = maxIdle;
    }


    /**
     * 利用可能なコネクションがない場合に例外を投げるまでプールが待機するミリセカンドの最大値です。
     * -1が設定された場合には期限を持たず待機します。
     * {@primary The maximum number of milliseconds that the pool will wait (when there
     * are no available connections) for a connection to be returned before
     * throwing an exception, or -1 to wait indefinitely.}
     */
    protected long maxWait = GenericObjectPool.DEFAULT_MAX_WAIT;

    public long getMaxWait() {
        return (this.maxWait);
    }

    public void setMaxWait(long maxWait) {
        this.maxWait = maxWait;
    }



    /**
     * [読取専用] 現在このデータソースから割り当てられているアクティブなコネクションの数を取得します。
     * {@primary [Read Only] The current number of active connections that have been
     * allocated from this data source.}
     */
    public int getNumActive() {
        if (connectionPool != null) {
            return (connectionPool.getNumActive());
        } else {
            return (0);
        }
    }


    /**
     * [読取専用] 現在このデータソース内で割り当てられるのを待っているアイドル状態のコネクションの数を取得します。
     * {@primary [Read Only] The current number of idle connections that are waiting
     * to be allocated from this data source.}
     */
    public int getNumIdle() {
        if (connectionPool != null) {
            return (connectionPool.getNumIdle());
        } else {
            return (0);
        }
    }


    /**
     * コネクションを確立する際に JDBC ドライバに渡されるコネクションパスワードです。
     * {@primary The connection password to be passed to our JDBC driver to establish
     * a connection.}
     */
    protected String password = null;

    public String getPassword() {
        return (this.password);
    }

    public void setPassword(String password) {
        this.password = password;
    }


    /**
     * コネクションを確立する際に JDBC ドライバに渡されるコネクションURLです。
     * {@primary The connection URL to be passed to our JDBC driver to establish
     * a connection.}
     */
    protected String url = null;

    public String getUrl() {
        return (this.url);
    }

    public void setUrl(String url) {
        this.url = url;
    }


    /**
     * コネクションを確立する際に JDBC ドライバに渡されるコネクションユーザ名です。
     * {@primary The connection username to be passed to our JDBC driver to
     * establish a connection.}
     */
    protected String username = null;

    public String getUsername() {
        return (this.username);
    }

    public void setUsername(String username) {
        this.username = username;
    }


    /**
     * このプールにコネクションが返された後にコネクションのチェックに使用される SQL クエリです。
     * 指定する場合にはこのクエリは少なくとも1つの列を返す SQL SELECT ステートメントで<strong>なくてはなりません</strong>。
     * {@primary The SQL query that will be used to validate connections from this pool
     * before returning them to the caller.  If specified, this query
     * <strong>MUST</strong> be an SQL SELECT statement that returns at least
     * one row.}
     */
    protected String validationQuery = null;

    public String getValidationQuery() {
        return (this.validationQuery);
    }

    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }


    // ----------------------------------------------------- Instance Variables


    /**
     * 内部でコネクションを管理するオブジェクトプールです。
     * {@primary The object pool that internally manages our connections.}
     */
    protected GenericObjectPool connectionPool = null;


    /**
     * 新たなコネクションを確立する際に JDBC ドライバに渡されるコネクションプロパティです。
     * <strong>注</strong> - "user" と "password" のプロパティは他の場所で設定されるため、ここに含まれる必要はありません。
     * {@primary The connection properties that will be sent to our JDBC driver when
     * establishing new connections.  <strong>NOTE</strong> - The "user" and
     * "password" properties will be passed explicitly, so they do not need
     * to be included here.}
     */
    protected Properties connectionProperties = new Properties();


    /**
     * コネクションの管理に使用するデータソースです。
     * このオブジェクトは <code>createDataSource()</code>
     * メソッドを呼ぶことによって<strong>のみ</strong>取得されるべきです。
     * {@primary The data source we will use to manage connections.  This object should
     * be acquired <strong>ONLY</strong> by calls to the
     * <code>createDataSource()</code> method.}
     */
    protected DataSource dataSource = null;


    /**
     * メッセージを直接ログする対象となる PrintWriter です。
     * {@primary The PrintWriter to which log messages should be directed.}
     */
    protected PrintWriter logWriter = new PrintWriter(System.out);


    // ----------------------------------------------------- DataSource Methods


    /**
     * データベースへのコネクションを(必要であれば)生成し、返します。
     * {@primary Create (if necessary) and return a connection to the database.}
     *
     * @exception SQLException データベースアクセスで例外が発生した場合
     * {@primary if a database access error occurs}
     */
    public Connection getConnection() throws SQLException {

        return (createDataSource().getConnection());

    }


    /**
     * データベースへのコネクションを(必要であれば)生成し、返します。
     * {@primary Create (if necessary) and return a connection to the database.}
     *
     * @param username コネクションを生成するためのデータベースユーザ
     * {@primary Database user on whose behalf the Connection
     *   is being made}
     * @param password データベースユーザのパスワード
     * {@primary The database user's password}
     *
     * @exception SQLException データベースアクセスで例外が発生した場合
     * {@primary if a database access error occurs}
     */
    public Connection getConnection(String username, String password)
        throws SQLException {

        return (createDataSource().getConnection(username, password));

    }


    /**
     * データベースに接続する際のログインタイムアウト時間(秒)を返します。
     * {@primary Return the login timeout (in seconds) for connecting to the database.}
     *
     * @exception SQLException データベースアクセスで例外が発生した場合
     * {@primary if a database access error occurs}
     */
    public int getLoginTimeout() throws SQLException {

        return (createDataSource().getLoginTimeout());

    }


    /**
     * このデータソースで使用されるログライターを返します。
     * {@primary Return the log writer being used by this data source.}
     *
     * @exception SQLException データベースアクセスで例外が発生した場合
     * {@primary if a database access error occurs}
     */
    public PrintWriter getLogWriter() throws SQLException {

        return (createDataSource().getLogWriter());

    }


    /**
     * データベースに接続する際のログインタイムアウト時間(秒)を設定します。
     * {@primary Set the login timeout (in seconds) for connecting to the database.}
     *
     * @param loginTimeout 新たなログインタイムアウト時間、0の場合タイムアウトしません
     * {@primary The new login timeout, or zero for no timeout}
     *
     * @exception SQLException データベースアクセスで例外が発生した場合
     * {@primary if a database access error occurs}
     */
    public void setLoginTimeout(int loginTimeout) throws SQLException {

        createDataSource().setLoginTimeout(loginTimeout);

    }


    /**
     * このデータソースで使用されるログライターを設定します。
     * {@primary Set the log writer being used by this data source.}
     *
     * @param logWriter 新たなログライター
     * {@primary The new log writer}
     *
     * @exception SQLException データベースアクセスで例外が発生した場合
     * {@primary if a database access error occurs}
     */
    public void setLogWriter(PrintWriter logWriter) throws SQLException {

        createDataSource().setLogWriter(logWriter);
        this.logWriter = logWriter;

    }

    private AbandonedConfig abandonedConfig;

    /**                       
     * removeAbandonedTimeout を超えた場合に破棄されたコネクションを削除するかどうかのフラグです。
     *
     * true か false を設定してください。デフォルトは false です。
     * true を設定した場合、アイドル時間が removeAbandonedTimeout 
     * を超えたコネクションは破棄され、削除されるべきであるとみなされます。
     * true を設定することによりコネクションのクローズ処理を失敗したときの処理があまり考慮されていない
     * アプリケーションのデータベースコネクションを回復させることができます。
     *
     * {@primary Flag to remove abandoned connections if they exceed the
     * removeAbandonedTimeout.
     *
     * Set to true or false, default false.
     * If set to true a connection is considered abandoned and eligible
     * for removal if it has been idle longer than the removeAbandonedTimeout.
     * Setting this to true can recover db connections from poorly written    
     * applications which fail to close a connection.}
     */                                                                   
    public boolean getRemoveAbandoned() {   
        if (abandonedConfig != null) {
            return abandonedConfig.getRemoveAbandoned();
        }
        return false;
    }                                    
                                      
    public void setRemoveAbandoned(boolean removeAbandoned) {
        if (abandonedConfig == null) {
            abandonedConfig = new AbandonedConfig();
        }
        abandonedConfig.setRemoveAbandoned(removeAbandoned);
    }                                                        
                                               
    /**
     * 破棄されたコネクションを削除する処理のタイムアウト時間(秒)です。
     * デフォルトは300秒です。
     * {@primary Timeout in seconds before an abandoned connection can be removed.
     *
     * Defaults to 300 seconds.}
     */                                                                 
    public int getRemoveAbandonedTimeout() { 
        if (abandonedConfig != null) {
            return abandonedConfig.getRemoveAbandonedTimeout();
        }
        return 300;
    }                                        
                                             
    public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {
        if (abandonedConfig == null) {
            abandonedConfig = new AbandonedConfig();
        }
        abandonedConfig.setRemoveAbandonedTimeout(removeAbandonedTimeout);
    }                                                                  
                                                             
    /**
     * ステートメントまたはコネクションを破棄したアプリケーションのスタックトレースをログするかどうかのフラグです。
     * デフォルトの値は false です。
     * 破棄されたステートメントまたはコネクションのログ処理は
     * 全てのコネクションを開く処理またはステートメントの生成時にスタックトレースを生成するためオーバヘッドを与えます。
     *
     * {@primary Flag to log stack traces for application code which abandoned
     * a Statement or Connection.
     *
     * Defaults to false.
     *
     * Logging of abandoned Statements and Connections adds overhead
     * for every Connection open or new Statement because a stack
     * trace has to be generated.}
     */                                                          
    public boolean getLogAbandoned() {   
        if (abandonedConfig != null) {
            return abandonedConfig.getLogAbandoned();
        }
        return false;
    }                                 
                                   
    public void setLogAbandoned(boolean logAbandoned) {
        if (abandonedConfig == null) {
            abandonedConfig = new AbandonedConfig();
        }
        abandonedConfig.setLogAbandoned(logAbandoned);
    }


    // --------------------------------------------------------- Public Methods


    /**
     * JDBC ドライバに渡すプロパティのセットにカスタムコネクションプロパティを追加します。
     * これは最初のコネクションを取得する前に(他の接続の設定と共に)行わなくてはなりません。
     * {@primary Add a custom connection property to the set that will be passed to our
     * JDBC driver.    This <strong>MUST</strong> be called before the first
     * connection is retrieved (along with all the other configuration
     * property setters).}
     *
     * @param name カスタムコネクションプロパティの名前
     * {@primary Name of the custom connection property}
     * @param value カスタムコネクションプロパティの値
     * {@primary Value of the custom connection property}
     */
    public void addConnectionProperty(String name, String value) {

        connectionProperties.put(name, value);

    }


    /**
     * このデータソースに関連づいているコネクションプールに現在保持されている全てのコネクションをクローズし、開放します。
     * {@primary Close and release all connections that are currently stored in the
     * connection pool associated with our data source.}
     *
     * @exception SQLException データベースアクセスで例外が発生した場合
     * {@primary if a database access error occurs}
     */
    public void close() throws SQLException {
        GenericObjectPool oldpool = connectionPool;
        connectionPool = null;
        dataSource = null;
        try {
            oldpool.close();
        } catch(SQLException e) {
            throw e;
        } catch(RuntimeException e) {
            throw e;
        } catch(Exception e) {
            throw new SQLException(e.toString());
        }
    }


    // ------------------------------------------------------ Protected Methods


    /**
     * <p>
     * コネクションの管理に使用するデータソースを(必要であれば)生成し、返します。
     * {@primary Create (if necessary) and return the internal data source we are
     * using to manage our connections.}</p>
     *
     * <p>
     * <strong>実装の注</strong> - このメソッドの同期化を行わず "double-checked locking"
     * イディオムの使用を試みる誘惑にかられますが、
     * JVM の動作に関する正規のいくつかの最適化が行われた場合、このイディオムは正確に機能しません。
     * {@annotation "double-checked locking" の問題に関しては
     * <a href="http://www-6.ibm.com/jp/developerworks/java/020726/j_j-dcl.html">developerWorks</a>
     * に良い記事があります。}
     * {@primary <strong>IMPLEMENTATION NOTE</strong> - It is tempting to use the
     * "double checked locking" idiom in an attempt to avoid synchronizing
     * on every single call to this method.  However, this idiom fails to
     * work correctly in the face of some optimizations that are legal for
     * a JVM to perform.}</p>
     *
     * @exception SQLException オブジェクトプールが生成できなかった場合
     * {@primary if the object pool cannot be created.}
     */
    protected synchronized DataSource createDataSource()
        throws SQLException {

        // Return the pool if we have already created it
        if (dataSource != null) {
            return (dataSource);
        }

        // Load the JDBC driver class
        Class driverClass = null;
        try {
            driverClass = Class.forName(driverClassName);
        } catch (Throwable t) {
            String message = "Cannot load JDBC driver class '" +
                driverClassName + "'";
            logWriter.println(message);
            t.printStackTrace(logWriter);
            throw new SQLException(message);
        }

        // Create a JDBC driver instance
        Driver driver = null;
        try {
            driver = DriverManager.getDriver(url);
        } catch (Throwable t) {
            String message = "Cannot create JDBC driver of class '" +
                driverClassName + "' for connect URL '" + url + "'";
            logWriter.println(message);
            t.printStackTrace(logWriter);
            throw new SQLException(message);
        }

        // Create an object pool to contain our active connections
        connectionPool = new AbandonedObjectPool(null,abandonedConfig);
        connectionPool.setMaxActive(maxActive);
        connectionPool.setMaxIdle(maxIdle);
        connectionPool.setMaxWait(maxWait);
        if (validationQuery != null) {
            connectionPool.setTestOnBorrow(true);
        }

        // Set up the driver connection factory we will use
        if (username != null) {
            connectionProperties.put("user", username);
        } else {
            System.out.println(
                "DBCP DataSource configured without a 'username'");
        }
        if (password != null) {
            connectionProperties.put("password", password);
        } else {
            System.out.println(
                "DBCP DataSource configured without a 'password'");
        }
        DriverConnectionFactory driverConnectionFactory =
            new DriverConnectionFactory(driver, url, connectionProperties);

        // Set up the poolable connection factory we will use
        PoolableConnectionFactory connectionFactory = null;
        try {
            connectionFactory =
                new PoolableConnectionFactory(driverConnectionFactory,
                                              connectionPool,
                                              null, // FIXME - stmtPoolFactory?
                                              validationQuery,
                                              defaultReadOnly,
                                              defaultAutoCommit,
                                              abandonedConfig);
        } catch(RuntimeException e) {
            throw e;
        } catch(Exception e) {
            throw new SQLException(e.toString());
        }

        // Create and return the pooling data source to manage the connections
        dataSource = new PoolingDataSource(connectionPool);
        dataSource.setLogWriter(logWriter);
        return (dataSource);

    }


}
