package org.maachang.dao.dbms.pool;

import java.sql.Connection;

import javax.sql.DataSource;

import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.KeyedObjectPoolFactory;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.commons.pool.impl.StackKeyedObjectPoolFactory;

/**
 * DBCPコネクションプーリング.
 * 
 * @version 2007/10/18
 * @author masahito suzuki
 * @since MaachangDao 1.00
 */
class DbcpPooling {
    
    private static final int CHK_NO_CONNECTION_BY_COUNT = 3 ;
    private static final long CHK_NO_CONNECTION_BY_TIME = 30L * 1000L ;
    private static final int PS_SIZE = 3 ;
    private ObjectPool pool = null ;
    private DataSource dataSouce = null ;
    
    private DbcpPooling() {
        
    }
    
    /**
     * DB接続用オブジェクトプールを生成.
     */
    private static final ObjectPool getObjectPool( String driver,String url, String user, 
        String passwd,long keepAlive,int max,String checkSQL,boolean readOnly,boolean autoCommit ) throws Exception {
        if( driver == null || ( driver = driver.trim() ).length() <= 0 ||
            url == null || ( url = url.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        user = (user == null || ( user = user.trim() ).length() <= 0) ? null : user ;
        passwd = (passwd == null || ( passwd = passwd.trim() ).length() <= 0) ? null : passwd ;
        checkSQL = (checkSQL == null || ( checkSQL = checkSQL.trim() ).length() <= 0) ? null : checkSQL ;
        int chkCount = -1 ;
        if( keepAlive <= -1L ) {
            chkCount = ( CHK_NO_CONNECTION_BY_COUNT >= max ) ? max : CHK_NO_CONNECTION_BY_COUNT ;
        }
        Class.forName( driver, true, DriverManagerConnectionFactory.class.getClassLoader() ) ;
        ObjectPool pool = new GenericObjectPool(
            null,                                       // factoryなし.
            max,                                        // 同時に取り出すことができる最大数.
            GenericObjectPool.WHEN_EXHAUSTED_GROW,      // Pooling限界の場合は、コネクション作成.
            -1,                                         // Pooling取得できない場合の待機時間(なし).
            max,                                        // 待機コネクション最大保有数.
            0,                                          // 待機コネクション最小保有数.
            true,                                       // コネクション有効確認を行う.
            true,                                       // 未使用コネクション再利用時の有効確認を行う.
            CHK_NO_CONNECTION_BY_TIME,                  // 1度の未使用コネクションチェック間隔.
            chkCount,                                   // 1度の未使用コネクションチェック数.
            keepAlive,                                  // 未使用コネクション破棄の最小時間.
            false                                       // 未使用オブジェクト排除スレッドでチェックしない.
            ) ;
        ConnectionFactory conFactory = new DriverManagerConnectionFactory( url,user,passwd ) ;
        KeyedObjectPoolFactory psFactory = new StackKeyedObjectPoolFactory( (max*PS_SIZE),max ) ;
        new PoolableConnectionFactory( conFactory, pool, psFactory, checkSQL, readOnly, autoCommit ) ;
        return pool ;
    }
    
    /**
     * コンストラクタ.
     * @param driver 対象のJDBCドライバ名を設定します.
     * @param url JDBC接続先URLを設定します.
     * @param user JDBC接続ユーザ名を設定します.
     * @param passwd JDBC接続パスワードを設定します.
     * @param keepAlive アイドル時の破棄時間を設定します.
     * @param max プーリング最大数を設定します.
     * @param checkSQL 接続確認用SQL文を設定します.
     * @param readOnly 接続オブジェクトを読み込み専用にするか設定します.
     * @param autoCommit オートコミットを行う場合は[true]を設定します.
     * @exception Exception 例外.
     */
    public DbcpPooling( String driver,String url, String user, String passwd,long keepAlive,int max,
        String checkSQL,boolean readOnly,boolean autoCommit ) throws Exception {
        this.pool = getObjectPool( driver,url,user,passwd,keepAlive,max,checkSQL,readOnly,autoCommit ) ;
        this.dataSouce = new PoolingDataSource( pool ) ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        if( pool != null ) {
            try {
                pool.close() ;
            } catch( Exception e ) {
            }
        }
        pool = null ;
        dataSouce = null ;
    }
    
    /**
     * コネクションを取得.
     * @return Connection コネクションオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public Connection getConnection() throws Exception {
        return dataSouce.getConnection() ;
    }
    
    /**
     * 最大コネクション数を取得.
     * @return int 最大コネクション数が返されます.
     */
    public int max() {
        return ( ( GenericObjectPool )pool ).getMaxActive() ;
    }
    
    /**
     * 使用中のコネクション数を取得.
     * @return int 使用中のコネクション数が返されます.
     */
    public int active() {
        return pool.getNumActive() ;
    }
    
    /**
     * 未使用コネクション数を取得.
     * @return int 未使用中のコネクション数が返されます.
     */
    public int idle() {
        return pool.getNumIdle() ;
    }
}
