package org.apache.commons.dbcp.jdbc2pool;

/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001 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 acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and 
 *    "Apache Turbine" 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",
 *    "Apache Turbine", nor may "Apache" appear in their name, without 
 *    prior written permission of the Apache Software Foundation.
 *
 * 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/>.
 */
 
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import java.util.Hashtable;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import javax.naming.Name;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Referenceable;
import javax.naming.Reference;
import javax.naming.RefAddr;
import javax.naming.BinaryRefAddr;
import javax.naming.StringRefAddr;
import javax.naming.NamingException;
import javax.naming.spi.ObjectFactory;

import org.apache.commons.collections.FastHashMap;
import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.commons.pool.KeyedObjectPool;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.apache.commons.pool.impl.StackObjectPool;

/**
 * <p>
 * J2EE環境でのデプロイに適切なプーリングを行う <code>DataSource</code> です。
 * さまざまな設定オプションを持ちます。
 * 複数のユーザがそれぞれの Connection の最大数等のパラメータの共通セットを持つことができます。
 * このプールはデプロイする環境がプロパティのマッピングによる初期化をサポートする場合、ユーザ毎の個々のプールを持つ機能をサポートします。
 * 従って、例えば管理者、または書き込みアクセスのプールに対して一定の Connection
 * を保証し、読み込み専用の接続の数とは切り離して管理することができます。
 * {@primary A pooling <code>DataSource</code> appropriate for deployment within
 * J2EE environment.  There are many configuration options.  Multiple users
 * can share a common set of parameters, such as a single maximum number
 * of Connections.  The pool can also support individual pools per user, if the
 * deployment environment can support initialization of mapped properties.
 * So for example, a pool of admin or write-access Connections can be
 * guaranteed a certain number of connections, separate from a maximum
 * set for read-only connections. }
 * </p>
 *
 * <p>
 * J2EE コンテナは普通 <code>DataSource</code> を初期化するためのいくつかの方法を提供し、
 * 初期化の為の属性は bean のゲッタ/セッタにて参照され、JNDI を経てデプロイされます。
 * これはプールされた論理的なデータベースへの接続の供給元としてとしてアプリケーションから利用することができます。
 * プールは物理的な接続の供給元を必要とします。
 * この供給元となる <code>ConnectionPoolDataSource</code> は JNDI を介してルックアップを行う
 * {@link #setDataSourceName(String)} にて指定することができます。
 * {@primary A J2EE container will normally provide some method of initializing the
 * <code>DataSource</code> whose attributes are presented
 * as bean getters/setters and then deploying it via JNDI.  It is then
 * available to an application as a source of pooled logical connections to 
 * the database.  The pool needs a source of physical connections.  This
 * source is in the form of a <code>ConnectionPoolDataSource</code> that
 * can be specified via the {@link #setDataSourceName(String)} used to
 * lookup the source via JNDI.}
 * </p>
 *
 * <p>
 * 普通、 Jdbc2PoolDataSource は JNDI 環境で使用されますが、
 * DriverAdapterCPDS は他の bean と同じようにインスタンス化、初期化されます。
 * この場合、 <code>ConnectionPoolDataSource</code> も同じような方法でインスタンス化されます。
 * この(コネクションの)供給元は {@link #setConnectionPoolDataSource(ConnectionPoolDataSource)}
 * メソッドを使用して直接このプールにアタッチされます。 
 * {@primary Although normally used within a JNDI environment, Jdbc2PoolDataSource
 * can be instantiated and initialized as any bean.  In this case the 
 * <code>ConnectionPoolDataSource</code> will likely be instantiated in
 * a similar manner.  The source can then be attached directly to this
 * pool using the 
 * {@link #setConnectionPoolDataSource(ConnectionPoolDataSource)} method.}
 * </p>
 *
 * <p>
 * この <code>DataSource</code> が JNDI を経由して複数の要求を受けた場合、ルックアップ毎に状態を保持します。
 * また、この複数のインスタンスはそれぞれ(コネクションの)供給元に異なる
 * <code>ConnectionPoolDataSource</code> を使用してデプロイされます。
 * {@primary If this <code>DataSource</code> 
 * is requested via JNDI multiple times, it maintains
 * state between lookups.  Also, multiple instances can be deployed using 
 * different backend <code>ConnectionPoolDataSource</code> sources.}  
 * </p>
 *
 * <p>
 * dbcp.cpdsadapter パッケージには Jdbc2PoolDataSource が使用することのできる
 * <code>ConnectionPoolDataSource</code> の実装は持たないが {@link java.sql.Driver}
 * は提供されている JDBC ドライバ実装のためのアダプタ
 * {@link org.apache.commons.dbcp.cpdsadapter.DriverAdapterCPDS} が含まれます。
 * {@primary The dbcp package contains an adapter, 
 * {@link org.apache.commons.dbcp.cpdsadapter.DriverAdapterCPDS},
 * that can be used to allow the
 * use of Jdbc2PoolDataSource with jdbc driver implementations that
 * do not supply a <code>ConnectionPoolDataSource</code>, but still
 * provide a {@link java.sql.Driver} implementation.}
 * </p>
 *
 * <p>
 * <a href="package-summary.html">パッケージドキュメント</a> には
 * catalina と JNDI を使用した場合と JNDI を使用しない場合の使用例が含まれています。
 * {@primary The <a href="package-summary.html">package documentation</a> contains an 
 * example using catalina and JNDI and it also contains a non-JNDI example. }
 * </p>
 *
 * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
 * @translator 日置 聡
 * @status firstdraft
 * @update 2003/09/12
 * @version $Id: Jdbc2PoolDataSource.java,v 1.1.1.1 2004/02/13 10:02:03 hioki Exp $
 */
public class Jdbc2PoolDataSource
    implements DataSource, Referenceable, Serializable, ObjectFactory
{
    private static final String GET_CONNECTION_CALLED = 
        "A Connection was already requested from this source, " + 
        "further initialization is not allowed.";

    private static Map dsInstanceMap = new HashMap();

    private static ObjectPool userPassKeyPool = 
        new StackObjectPool(new UserPassKey.Factory(), 256);

    private static ObjectPool poolKeyPool = 
        new StackObjectPool(new PoolKey.Factory(), 256);

    private boolean getConnectionCalled = false;

    private ConnectionPoolDataSource cpds;
    /** DataSource Name used to find the ConnectionPoolDataSource */
    private String dataSourceName;
    private boolean defaultAutoCommit;
    private int defaultMaxActive = 0;
    private int defaultMaxIdle = 0;
    private int defaultMaxWait = -1;
    private boolean defaultReadOnly;
    /** Description */
    private String description;
    /** Environment that may be used to set up a jndi initial context. */
    private Properties jndiEnvironment;
    /** Login TimeOut in seconds */
    private int loginTimeout;
    /** Log stream */
    private PrintWriter logWriter;
    private Map perUserDefaultAutoCommit;    
    private Map perUserMaxActive;    
    private Map perUserMaxIdle;    
    private Map perUserMaxWait;
    private Map perUserDefaultReadOnly;    
    private boolean _testOnBorrow;
    private boolean _testOnReturn;
    private int _timeBetweenEvictionRunsMillis;
    private int _numTestsPerEvictionRun;
    private int _minEvictableIdleTimeMillis;
    private boolean _testWhileIdle;
    private String validationQuery = null;
    private boolean testPositionSet;

    private boolean isNew;
    private Integer instanceKey;

    /**
     * Serialization のための引数のないデフォルトコンストラクタ。
     * {@primary Default no-arg constructor for Serialization}
     */
    public Jdbc2PoolDataSource() 
    {
        isNew = true;
        defaultAutoCommit = true;
    }

    /**
     * PooledConnection が既にリクエストされていた場合、
     * IllegalStateException を投げます。
     * {@primary Throws an IllegalStateException, if a PooledConnection has already
     * been requested.}
     */
    private void assertInitializationAllowed()
        throws IllegalStateException 
    {
        if (getConnectionCalled) 
        {
            throw new IllegalStateException(GET_CONNECTION_CALLED);
        }
    }

    // -------------------------------------------------------------------
    // Properties

    /**
     * connectionPoolDataSource の値を取得します。
     * 内部のデータソースが JNDI を介してアクセスされている場合には null を返します。
     * {@primary Get the value of connectionPoolDataSource.  This method will return
     * null, if the backing datasource is being accessed via jndi.}
     *
     * @return connectionPoolDataSource の値
     * {@primary value of connectionPoolDataSource.}
     */
    public ConnectionPoolDataSource getConnectionPoolDataSource() 
    {
        return cpds;
    }
    
    /**
     * バックエンドの ConnectionPoolDataSource を設定します。
     * データソースのアクセスに JNDI を使用する場合には、このプロパティは設定するべきではありません。
     * {@primary Set the backend ConnectionPoolDataSource.  This property should not be
     * set if using jndi to access the datasource.}
     *
     * @param v  connectionPoolDataSource に割り当てる値
     * {@primary Value to assign to connectionPoolDataSource.}
     */
    public void setConnectionPoolDataSource(ConnectionPoolDataSource  v) 
    {
        assertInitializationAllowed();
        if (dataSourceName != null) 
        {
            throw new IllegalStateException(
                "Cannot set the DataSource, if JNDI is used.");
        }
        this.cpds = v;
        if (isNew) 
        {
            registerInstance();
        }
    }

    /**
     * このプールの内部の ConnectionPoolDataSource の名前を取得します。
     * この名前は JNDI サービスプロバイダからデータソースをルックアップする際に使用されます。
     * {@primary Get the name of the ConnectionPoolDataSource which backs this pool.
     * This name is used to look up the datasource from a jndi service 
     * provider.}
     *
     * @return dataSourceName の値
     * {@primary value of dataSourceName.}
     */
    public String getDataSourceName() 
    {
        return dataSourceName;
    }
    
    /**
     * このプールの内部の ConnectionPoolDataSource の名前を設定します。
     * この名前は JNDI サービスプロバイダからデータソースをルックアップする際に使用されます。
     * {@primary Set the name of the ConnectionPoolDataSource which backs this pool.
     * This name is used to look up the datasource from a jndi service 
     * provider.}
     *
     * @param v  dataSourceName に割り当てる値
     * {@primary Value to assign to dataSourceName.}
     */
    public void setDataSourceName(String  v) 
    {
        assertInitializationAllowed();
        if (cpds != null) 
        {
            throw new IllegalStateException(
                "Cannot set the JNDI name for the DataSource, if already " +
                "set using setConnectionPoolDataSource.");
        }
        this.dataSourceName = v;
        if (isNew) 
        {
            registerInstance();
        }
    }

    
    /** 
     * このプールから取り出されるコネクションの状態を定義する defaultAutoCommit の値を取得します。
     * この設定は Connection の Connection.setAutoCommit(boolean) を使用して変更することができます。
     * デフォルトの値は true です。
     * {@primary Get the value of defaultAutoCommit, which defines the state of 
     * connections handed out from this pool.  The value can be changed
     * on the Connection using Connection.setAutoCommit(boolean).
     * The default is true.}
     *
     * @return defaultAutoCommit の値
     * {@primary value of defaultAutoCommit.} 
     */
    public boolean isDefaultAutoCommit() 
    {
        return defaultAutoCommit;
    }
    
    /**
     * このプールから取り出されるコネクションの状態を定義する defaultAutoCommit の値を設定します。
     * この設定は Connection の Connection.setAutoCommit(boolean) を使用して変更することができます。
     * デフォルトの値は true です。
     * {@primary Set the value of defaultAutoCommit, which defines the state of 
     * connections handed out from this pool.  The value can be changed
     * on the Connection using Connection.setAutoCommit(boolean).
     * The default is true.}
     *
     * @param v  defaultAutoCommit に割り当てる値
     * {@primary Value to assign to defaultAutoCommit.}
     */
    public void setDefaultAutoCommit(boolean  v) 
    {
        assertInitializationAllowed();
        this.defaultAutoCommit = v;
    }


    /**
     * このプールから1度に割り当てることのできるアクティブなコネクションの最大数です。
     * 0が設定された場合には制限を行いません。
     * この値は perUserMaxConnections が指定されていない全てのユーザ名に対して適用されます。
     * デフォルトの値は0です。
     * {@primary The maximum number of active connections that can be allocated from
     * this pool at the same time, or zero for no limit.
     * This value is used for any username which is not specified
     * in perUserMaxConnections.  The default is 0.}
     */
    public int getDefaultMaxActive() {
        return (this.defaultMaxActive);
    }

    /**
     * このプールから1度に割り当てることのできるアクティブなコネクションの最大数です。
     * 0が設定された場合には制限を行いません。
     * この値は perUserMaxConnections が指定されていない全てのユーザ名に対して適用されます。
     * デフォルトの値は0です。
     * {@primary The maximum number of active connections that can be allocated from
     * this pool at the same time, or zero for no limit.
     * This value is used for any username which is not specified
     * in perUserMaxConnections.  The default is 0.}
     */
    public void setDefaultMaxActive(int maxActive) {
        assertInitializationAllowed();
        this.defaultMaxActive = maxActive;
    }


    /**
     * このプール内で開放されずにアイドル状態でいられるアクティブなコネクションの最大数です。
     * 0が設定された場合には制限を行いません。
     * この値は perUserMaxIdle が指定されていない全てのユーザ名に対して適用されます。
     * デフォルトの値は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.
     * This value is used for any username which is not specified
     * in perUserMaxIdle.  The default is 0.}
     */
    public int getDefaultMaxIdle() {
        return (this.defaultMaxIdle);
    }

    /**
     * このプール内で開放されずにアイドル状態でいられるアクティブなコネクションの最大数です。
     * 0が設定された場合には制限を行いません。
     * この値は perUserMaxIdle が指定されていない全てのユーザ名に対して適用されます。
     * デフォルトの値は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.
     * This value is used for any username which is not specified
     * in perUserMaxIdle.  The default is 0.}
     */
    public void setDefaultMaxIdle(int defaultMaxIdle) {
        assertInitializationAllowed();
        this.defaultMaxIdle = defaultMaxIdle;
    }


    /**
     * 利用可能なコネクションがない場合に例外を投げるまでプールが待機するミリセカンドの最大値です。
     * -1が設定された場合には期限を持たず待機します。
     * 利用可能なコネクションがない場合に直ちに失敗としたい場合には0を設定します。
     * この値は perUserMaxWait が指定されていない全てのユーザ名に対して適用されます。
     * デフォルトの値は-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.  Will fail 
     * immediately if value is 0.
     * This value is used for any username which is not specified
     * in perUserMaxWait.  The default is -1.}
     */
    public int getDefaultMaxWait() {
        return (this.defaultMaxWait);
    }

    /**
     * 利用可能なコネクションがない場合に例外を投げるまでプールが待機するミリセカンドの最大値です。
     * -1が設定された場合には期限を持たず待機します。
     * 利用可能なコネクションがない場合に直ちに失敗としたい場合には0を設定します。
     * この値は perUserMaxWait が指定されていない全てのユーザ名に対して適用されます。
     * デフォルトの値は-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.  Will fail 
     * immediately if value is 0.
     * This value is used for any username which is not specified
     * in perUserMaxWait.  The default is -1.}
     */
    public void setDefaultMaxWait(int defaultMaxWait) {
        assertInitializationAllowed();
        this.defaultMaxWait = defaultMaxWait;
    }


    /**
     * このプールから取り出されるコネクションの状態を定義する defaultReadOnly の値を取得します。
     * この設定は Connection の Connection.setReadOnly(boolean) を使用して変更することができます。
     * デフォルトの値は false です。
     * {@primary Get the value of defaultReadOnly, which defines the state of 
     * connections handed out from this pool.  The value can be changed
     * on the Connection using Connection.setReadOnly(boolean).
     * The default is false.}
     *
     * @return defaultReadOnly の値
     * {@primary value of defaultReadOnly.}
     */
    public boolean isDefaultReadOnly() 
    {
        return defaultReadOnly;
    }
    
    /**
     * このプールから取り出されるコネクションの状態を定義する defaultReadOnly の値を設定します。
     * この設定は Connection の Connection.setReadOnly(boolean) を使用して変更することができます。
     * デフォルトの値は false です。
     * {@primary Set the value of defaultReadOnly, which defines the state of 
     * connections handed out from this pool.  The value can be changed
     * on the Connection using Connection.setReadOnly(boolean).
     * The default is false.}
     *
     * @param v  defaultReadOnly に割り当てる値
     * {@primary Value to assign to defaultReadOnly.}
     */
    public void setDefaultReadOnly(boolean  v) 
    {
        assertInitializationAllowed();
        this.defaultReadOnly = v;
    }

    
    /**
     * 記述情報(Description)の値を取得します。
     * このプロパティはデータソースをデプロイする GUI 等のツールで使用するために JDBC にて定義されています。
     * このプロパティは内部の処理には何の影響も与えません。
     * {@primary Get the description.  This property is defined by jdbc as for use with
     * GUI (or other) tools that might deploy the datasource.  It serves no
     * internal purpose.}
     *
     * @return 記述情報(Description)の値
     * {@primary value of description.}
     */
    public String getDescription() 
    {
        return description;
    }
    
    /**
     * 記述情報(Description)の値を設定します。
     * このプロパティはデータソースをデプロイする GUI 等のツールで使用するために JDBC にて定義されています。
     * このプロパティは内部の処理には何の影響も与えません。
     * {@primary Set the description.  This property is defined by jdbc as for use with
     * GUI (or other) tools that might deploy the datasource.  It serves no
     * internal purpose.}
     * 
     * @param v  記述情報(Description)に割り当てる値
     * {@primary Value to assign to description.}
     */
    public void setDescription(String  v) 
    {
        this.description = v;
    }
        

    /**
     * JNDI InitialContext のインスタンス化の際に使用される jndiEnvironment の値を取得します。
     * この InitialContext はバックエンドの ConnectionPoolDataSource の場所を確認するために使用されます。
     * {@primary Get the value of jndiEnvironment which is used when instantiating
     * a jndi InitialContext.  This InitialContext is used to locate the
     * backend ConnectionPoolDataSource.}
     *
     * @return jndiEnvironment の値
     * {@primary value of jndiEnvironment.}
     */
    public String getJndiEnvironment(String key) 
    {
        String value = null;
        if (jndiEnvironment != null) 
        {
            value = jndiEnvironment.getProperty(key);
        }
        return value;
    }
    
    /**
     * JNDI InitialContext のインスタンス化の際に使用される jndiEnvironment の値を設定します。
     * この InitialContext はバックエンドの ConnectionPoolDataSource の場所を確認するために使用されます。
     * {@primary Set the value of jndiEnvironment which is used when instantiating
     * a jndi InitialContext.  This InitialContext is used to locate the
     * backend ConnectionPoolDataSource.}
     *
     * @param value  jndiEnvironment に割り当てる値
     * {@primary Value to assign to jndiEnvironment.}
     */
    public void setJndiEnvironment(String key, String value) 
    {
        if (jndiEnvironment == null) 
        {
            jndiEnvironment = new Properties();
        }
        jndiEnvironment.setProperty(key, value);
    }

    
    /**
     * loginTimeout の値を取得します。
     * {@primary Get the value of loginTimeout.}
     *
     * @return loginTimeout の値
     * {@primary value of loginTimeout.}
     */
    public int getLoginTimeout() 
    {
        return loginTimeout;
    }
    
    /**
     * loginTimeout の値を設定します。
     * {@primary Set the value of loginTimeout.}
     *
     * @param v  loginTimeout に割り当てる値
     * {@primary Value to assign to loginTimeout.}
     */
    public void setLoginTimeout(int  v) 
    {
        this.loginTimeout = v;
    }
    
    
    /**
     * logWriter の値を取得します。
     * {@primary Get the value of logWriter.}
     *
     * @return logWriter の値
     * {@primary value of logWriter.}
     */
    public PrintWriter getLogWriter() 
    {
        if (logWriter == null) 
        {
            logWriter = new PrintWriter(System.out);
        }        
        return logWriter;
    }
    
    /**
     * logWriter の値を設定します。
     * {@primary Set the value of logWriter.}
     *
     * @param v  logWriter に割り当てる値
     * {@primary Value to assign to logWriter.}
     */
    public void setLogWriter(PrintWriter  v) 
    {
        this.logWriter = v;
    }
    

    /**
     * ユーザ毎のオートコミットのデフォルトの設定値を取得します。
     * {@note <br/>コメントが不適切と思われるので書き換えました。 }
     * {@primary The keys are usernames and the value is the --.  Any 
     * username specified here will override the value of defaultAutoCommit.}
     */
    public Boolean getPerUserDefaultAutoCommit(String key) 
    {
        Boolean value = null;
        if (perUserDefaultAutoCommit != null) 
        {
            value = (Boolean)perUserDefaultAutoCommit.get(key);
        }
        return value;
    }
    
    /**
     * ユーザ毎のオートコミットのデフォルトの設定値を設定します。
     * {@note <br/>コメントが不適切と思われるので書き換えました。 }
     * {@primary The keys are usernames and the value is the --.  Any 
     * username specified here will override the value of defaultAutoCommit.}
     */
    public void setPerUserDefaultAutoCommit(String username, Boolean value) 
    {
        assertInitializationAllowed();
        if (perUserDefaultAutoCommit == null) 
        {
            perUserDefaultAutoCommit = new HashMap();
        }
        perUserDefaultAutoCommit.put(username, value);
    }

    
    /**
     * ユーザ毎のこのプールから1度に割り当てることのできるアクティブなステートメントの最大数です。
     * 0が設定された場合には制限を行いません。
     * {@note <br/>コメントが不適切と思われるので書き換えました。 }
     * {@primary The maximum number of active connections that can be allocated from
     * this pool at the same time, or zero for no limit.
     * The keys are usernames and the value is the maximum connections.  Any 
     * username specified here will override the value of defaultMaxActive.}
     */
    public Integer getPerUserMaxActive(String username) 
    {
        Integer value = null;
        if (perUserMaxActive != null) 
        {
            value = (Integer)perUserMaxActive.get(username);
        }
        return value;
    }
    
    /**
     * ユーザ毎のこのプールから1度に割り当てることのできるアクティブなステートメントの最大数です。
     * 0が設定された場合には制限を行いません。
     * {@note <br/>コメントが不適切と思われるので書き換えました。 }
     * {@primary The maximum number of active connections that can be allocated from
     * this pool at the same time, or zero for no limit.
     * The keys are usernames and the value is the maximum connections.  Any 
     * username specified here will override the value of defaultMaxActive.}
     */
    public void setPerUserMaxActive(String username, Integer value) 
    {
        assertInitializationAllowed();
        if (perUserMaxActive == null) 
        {
            perUserMaxActive = new HashMap();
        }
        perUserMaxActive.put(username, value);
    }


    /**
     * ユーザ毎の余分なものを開放せずにプール内にアイドル状態で保持できるステートメントの最大数です。
     * 0が設定された場合には制限を行いません。
     * {@note <br/>コメントが不適切と思われるので書き換えました。 }
     * {@primary The maximum number of active connections that can remain idle in the
     * pool, without extra ones being released, or zero for no limit.
     * The keys are usernames and the value is the maximum connections.  Any 
     * username specified here will override the value of defaultMaxIdle.}
     */
    public Integer getPerUserMaxIdle(String username) 
    {
        Integer value = null;
        if (perUserMaxIdle != null) 
        {
            value = (Integer)perUserMaxIdle.get(username);
        }
        return value;
    }
    
    /**
     * ユーザ毎の余分なものを開放せずにプール内にアイドル状態で保持できるステートメントの最大数です。
     * 0が設定された場合には制限を行いません。
     * {@note <br/>コメントが不適切と思われるので書き換えました。 }
     * {@primary The maximum number of active connections that can remain idle in the
     * pool, without extra ones being released, or zero for no limit.
     * The keys are usernames and the value is the maximum connections.  Any 
     * username specified here will override the value of defaultMaxIdle.}
     */
    public void setPerUserMaxIdle(String username, Integer value) 
    {
        assertInitializationAllowed();
        if (perUserMaxIdle == null) 
        {
            perUserMaxIdle = new HashMap();
        }
        perUserMaxIdle.put(username, value);
    }
    
    /**
     * ユーザ毎の利用可能なコネクションがない場合に例外を投げるまでプールが待機するミリセカンドの最大値です。
     * -1が設定された場合には期限を持たず待機します。
     * 利用可能なコネクションがない場合に直ちに失敗としたい場合には0を設定します。
     * {@note <br/>コメントが不適切と思われるので書き換えました。 }
     * {@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.  Will fail 
     * immediately if value is 0.
     * The keys are usernames and the value is the maximum connections.  Any 
     * username specified here will override the value of defaultMaxWait.}
     */
    public Integer getPerUserMaxWait(String username) 
    {
        Integer value = null;
        if (perUserMaxWait != null) 
        {
            value = (Integer)perUserMaxWait.get(username);
        }
        return value;
    }
    
    /**
     * ユーザ毎の利用可能なコネクションがない場合に例外を投げるまでプールが待機するミリセカンドの最大値です。
     * -1が設定された場合には期限を持たず待機します。
     * 利用可能なコネクションがない場合に直ちに失敗としたい場合には0を設定します。
     * {@note <br/>コメントが不適切と思われるので書き換えました。 }
     * {@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.  Will fail 
     * immediately if value is 0.
     * The keys are usernames and the value is the maximum connections.  Any 
     * username specified here will override the value of defaultMaxWait.}
     */
    public void setPerUserMaxWait(String username, Integer value) 
    {
        assertInitializationAllowed();
        if (perUserMaxWait == null) 
        {
            perUserMaxWait = new HashMap();
        }
        perUserMaxWait.put(username, value);
    }


    /**
     * ユーザ毎の読み込み専用アクセスのデフォルトの設定値(defaultReadOnly)を取得します。
     * {@note <br/>コメントが不適切と思われるので書き換えました。 }
     * {@primary The keys are usernames and the value is the --.  Any 
     * username specified here will override the value of defaultReadOnly.}
     */
    public Boolean getPerUserDefaultReadOnly(String username) 
    {
        Boolean value = null;
        if (perUserDefaultReadOnly != null) 
        {
            value = (Boolean)perUserDefaultReadOnly.get(username);
        }
        return value;
    }
    
    /**
     * ユーザ毎の読み込み専用アクセスのデフォルトの設定値(defaultReadOnly)を設定します。
     * {@note <br/>コメントが不適切と思われるので書き換えました。 }
     * {@primary The keys are usernames and the value is the --.  Any 
     * username specified here will override the value of defaultReadOnly.}
     */
    public void setPerUserDefaultReadOnly(String username, Boolean value) 
    {
        assertInitializationAllowed();
        if (perUserDefaultReadOnly == null) 
        {
            perUserDefaultReadOnly = new HashMap();
        }
        perUserDefaultReadOnly.put(username, value);
    }


    /**
     * この値が <tt>true</tt> の場合、 {@link org.apache.commons.pool.ObjectPool#borrowObject}
     * にてプールからオブジェクトが取り出される前に{@link org.apache.commons.pool.PoolableObjectFactory#validateObject チェック}が行われます。
     * チェックの結果、オブジェクトの状態が不適切と判断された場合このオブジェクトはプールから削除され、
     * 他のオブジェクトを取り出すことを試みます。
     * {@primary When <tt>true</tt>, objects will be
     * {@link PoolableObjectFactory#validateObject validated}
     * before being returned by the {@link #borrowObject}
     * method.  If the object fails to validate,
     * it will be dropped from the pool, and we will attempt
     * to borrow another.}
     *
     * @see #setTestOnBorrow
     */
    public boolean getTestOnBorrow() {
        return _testOnBorrow;
    }

    /**
     * この値が <tt>true</tt> の場合、 {@link org.apache.commons.pool.ObjectPool#borrowObject}
     * にてプールからオブジェクトが取り出される前に{@link org.apache.commons.pool.PoolableObjectFactory#validateObject チェック}が行われます。
     * チェックの結果、オブジェクトの状態が不適切と判断された場合このオブジェクトはプールから削除され、
     * 他のオブジェクトを取り出すことを試みます。
     * {@primary When <tt>true</tt>, objects will be
     * {@link PoolableObjectFactory#validateObject validated}
     * before being returned by the {@link #borrowObject}
     * method.  If the object fails to validate,
     * it will be dropped from the pool, and we will attempt
     * to borrow another.}
     *
     * @see #getTestOnBorrow
     */
    public void setTestOnBorrow(boolean testOnBorrow) {
        assertInitializationAllowed();
        _testOnBorrow = testOnBorrow;
        testPositionSet = true;
    }

    /**
     * この値が <tt>true</tt> の場合、 {@link org.apache.commons.pool.ObjectPool#returnObject}
     * にてプールへオブジェクトが返された後に{@link org.apache.commons.pool.PoolableObjectFactory#validateObject チェック}が行われます。
     * {@primary When <tt>true</tt>, objects will be
     * {@link PoolableObjectFactory#validateObject validated}
     * before being returned to the pool within the
     * {@link #returnObject}.}
     *
     * @see #setTestOnReturn
     */
    public boolean getTestOnReturn() {
        return _testOnReturn;
    }

    /**
     * この値が <tt>true</tt> の場合、 {@link org.apache.commons.pool.ObjectPool#returnObject}
     * にてプールへオブジェクトが返された後に{@link org.apache.commons.pool.PoolableObjectFactory#validateObject チェック}が行われます。
     * {@primary When <tt>true</tt>, objects will be
     * {@link PoolableObjectFactory#validateObject validated}
     * before being returned to the pool within the
     * {@link #returnObject}.}
     *
     * @see #getTestOnReturn
     */
    public void setTestOnReturn(boolean testOnReturn) {
        assertInitializationAllowed();
        _testOnReturn = testOnReturn;
        testPositionSet = true;
    }

    /**
     * アイドル状態のオブジェクトを排除するスレッドが次の動作までスリープする時間を示すミリセカンドの値を返します。
     * マイナスの値が設定されている場合、アイドル状態のオブジェクトを排除するスレッドは動作しません。
     * {@primary Returns the number of milliseconds to sleep between runs of the
     * idle object evictor thread.
     * When non-positive, no idle object evictor thread will be
     * run.}
     *
     * @see #setTimeBetweenEvictionRunsMillis
     */
    public int getTimeBetweenEvictionRunsMillis() {
        return _timeBetweenEvictionRunsMillis;
    }

    /**
     * アイドル状態のオブジェクトを排除するスレッドが次の動作までスリープする時間を示すミリセカンドの値を設定します。
     * マイナスの値が設定されている場合、アイドル状態のオブジェクトを排除するスレッドは動作しません。
     * {@primary Sets the number of milliseconds to sleep between runs of the
     * idle object evictor thread.
     * When non-positive, no idle object evictor thread will be
     * run.}
     *
     * @see #getTimeBetweenEvictionRunsMillis
     */
    public void 
        setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) {
        assertInitializationAllowed();
            _timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }

    /**
     * アイドル状態のオブジェクトを排除するスレッドが1度の動作でチェックの対象とするオブジェクトの数を返します。
     * {@primary Returns the number of objects to examine during each run of the
     * idle object evictor thread (if any).}
     *
     * @see #setNumTestsPerEvictionRun
     * @see #setTimeBetweenEvictionRunsMillis
     */
    public int getNumTestsPerEvictionRun() {
        return _numTestsPerEvictionRun;
    }

    /**
     * アイドル状態のオブジェクトを排除するスレッドが1度の動作でチェックの対象とするオブジェクトの数を設定します。
     * {@primary Sets the number of objects to examine during each run of the
     * idle object evictor thread (if any).}
     * <p>
     * マイナスの値が設定された場合、<tt>ceil({*link #numIdle})/abs({*link #getNumTestsPerEvictionRun})</tt> の数のテストが実施されます。
     * 例えば値が <i>-n</i> の場合にはアイドル状態のステートメントのうち <i>1/n</i> が1度の動作でテストされます。
     * {@primary When a negative value is supplied, <tt>ceil({@link #numIdle})/abs({@link #getNumTestsPerEvictionRun})</tt>
     * tests will be run.  I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the
     * idle objects will be tested per run.}
     *
     * @see #getNumTestsPerEvictionRun
     * @see #setTimeBetweenEvictionRunsMillis
     */
    public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
        assertInitializationAllowed();
        _numTestsPerEvictionRun = numTestsPerEvictionRun;
    }

    /**
     * アイドル状態のオブジェクトがプール内に居られる最短時間を返します。
     * この時間を過ぎるとアイドル状態のオブジェクトを排除するスレッドのチェック対象となります。
     * {@primary Returns the minimum amount of time an object may sit idle in the pool
     * before it is eligable for eviction by the idle object evictor
     * (if any).}
     *
     * @see #setMinEvictableIdleTimeMillis
     * @see #setTimeBetweenEvictionRunsMillis
     */
    public int getMinEvictableIdleTimeMillis() {
        return _minEvictableIdleTimeMillis;
    }

    /**
     * アイドル状態のオブジェクトがプール内に居られる最短時間を設定します。
     * この時間を過ぎるとアイドル状態のオブジェクトを排除するスレッドのチェック対象となります。
     * マイナスの値が設定された場合、アイドル状態で居た時間ではオブジェクトはプールから排除されません。
     * {@primary Sets the minimum amount of time an object may sit idle in the pool
     * before it is eligable for eviction by the idle object evictor
     * (if any).
     * When non-positive, no objects will be evicted from the pool
     * due to idle time alone.}
     *
     * @see #getMinEvictableIdleTimeMillis
     * @see #setTimeBetweenEvictionRunsMillis
     */
    public void 
        setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
        assertInitializationAllowed();
        _minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    /**
     * この値が <tt>true</tt> の場合、アイドル状態のオブジェクトを排除するスレッドにて
     * {@link org.apache.commons.pool.PoolableObjectFactory#validateObject チェック}が行われます。
     * チェックの結果、オブジェクトの状態が不適切と判断された場合にはプールから削除されます。
     * {@primary When <tt>true</tt>, objects will be
     * {@link PoolableObjectFactory#validateObject validated}
     * by the idle object evictor (if any).  If an object
     * fails to validate, it will be dropped from the pool.}
     *
     * @see #setTestWhileIdle
     * @see #setTimeBetweenEvictionRunsMillis
     */
    public boolean getTestWhileIdle() {
        return _testWhileIdle;
    }

    /**
     * この値が <tt>true</tt> の場合、アイドル状態のオブジェクトを排除するスレッドにて
     * {@link org.apache.commons.pool.PoolableObjectFactory#validateObject チェック}が行われます。
     * チェックの結果、オブジェクトの状態が不適切と判断された場合にはプールから削除されます。
     * {@primary When <tt>true</tt>, objects will be
     * {@link PoolableObjectFactory#validateObject validated}
     * by the idle object evictor (if any).  If an object
     * fails to validate, it will be dropped from the pool.}
     *
     * @see #getTestWhileIdle
     * @see #setTimeBetweenEvictionRunsMillis
     */
    public void setTestWhileIdle(boolean testWhileIdle) {
        assertInitializationAllowed();
        _testWhileIdle = testWhileIdle;
        testPositionSet = true;
    }


    /**
     * このプールにコネクションが返された後にコネクションのチェックに使用される 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.}
     */
    public String getValidationQuery() {
        return (this.validationQuery);
    }

    /**
     * このプールにコネクションが返された後にコネクションのチェックに使用される 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.  Default behavior is to test the connection when it is
     * borrowed.}

     */
    public void setValidationQuery(String validationQuery) {
        assertInitializationAllowed();
        this.validationQuery = validationQuery;
        if (!testPositionSet) 
        {
            setTestOnBorrow(true);
        }
    }

    // ----------------------------------------------------------------------
    // DataSource implementation 

    /**
     * データベースとの接続の確立を試みます。
     * {@primary Attempt to establish a database connection.}
     */
    public Connection getConnection() 
        throws SQLException
    {
        return getConnection(null, null);
    }

    /**
     * データベースとの接続の確立を試みます。
     * {@primary Attempt to establish a database connection.}
     */
    synchronized public Connection getConnection(String username, String password)
        throws SQLException
    {
        if (isNew) 
        {
            throw new SQLException("Must set the ConnectionPoolDataSource " + 
                "through setDataSourceName or setConnectionPoolDataSource " + 
                "before calling getConnection.");
        }
        getConnectionCalled = true;
        Map pools = (Map)dsInstanceMap.get(instanceKey);
        PoolKey key = getPoolKey(username);
        Object pool = pools.get(key);
        if ( pool == null ) 
        {
            try
            {
                registerPool(username, password);
                pool = pools.get(key);
            }
            catch (Exception e)
            {
                e.printStackTrace();
                throw new SQLException(e.getMessage());
            }
        }
        returnPoolKey(key);

        PooledConnection pc = null;
        if (pool instanceof ObjectPool) 
        {
            try
            {
                pc = (PooledConnection)((ObjectPool)pool).borrowObject();
            }
            catch (Exception e)
            {
                if (e instanceof RuntimeException) 
                {
                    throw (RuntimeException)e;
                }
                else 
                {
                    throw new SQLException(e.getMessage());
                }
            }
        }
        else // assume KeyedObjectPool
        { 
            try
            {
                UserPassKey upkey = getPCKey(username, password);
                pc = (PooledConnection)
                    ((KeyedObjectPool)pool).borrowObject(upkey);
                returnPCKey(upkey);
            }
            catch (Exception e)
            {
                if (e instanceof RuntimeException) 
                {
                    throw (RuntimeException)e;
                }
                else 
                {
                    throw new SQLException(e.getMessage());
                }
            }
        }
        
        boolean defaultAutoCommit = isDefaultAutoCommit();
        if ( username != null ) 
        {
            Boolean userMax = 
                getPerUserDefaultAutoCommit(username);
            if ( userMax != null ) 
            {
                defaultAutoCommit = userMax.booleanValue();
            }
        }    

        boolean defaultReadOnly = isDefaultReadOnly();
        if ( username != null ) 
        {
            Boolean userMax = 
                getPerUserDefaultReadOnly(username);
            if ( userMax != null ) 
            {
                defaultReadOnly = userMax.booleanValue();
            }
        }    

        Connection con = pc.getConnection();        
        con.setAutoCommit(defaultAutoCommit);
        con.setReadOnly(defaultReadOnly);
        return con;
    }

    private UserPassKey getPCKey(String username, String password)
    {
        UserPassKey upk = null;
        try
        {
            upk = (UserPassKey)userPassKeyPool.borrowObject();
        }
        catch (Exception e)
        {
            getLogWriter().println("[WARN] Jdbc2PoolDataSource::getPCKey"
                + " could not get key from pool. Created a new instance. "
                + e.getMessage());
            upk = new UserPassKey();
        }
        upk.init(username, password);
        return upk;
    }

    private void returnPCKey(UserPassKey key)
    {
        if (key.isReusable()) 
        {
            try
            {
                userPassKeyPool.returnObject(key);
            }
            catch (Exception e)
            {
                getLogWriter().println(
                    "[WARN] Jdbc2PoolDataSource::returnPCKey could not return"
                    + " key to pool. " + e.getMessage());
            }
        }
    }

    private PoolKey getPoolKey(String username)
    {
        PoolKey key = null;
        try
        {
            key = (PoolKey)poolKeyPool.borrowObject();
        }
        catch (Exception e)
        {
            getLogWriter().println("[WARN] Jdbc2PoolDataSource::getPoolKey"
                + " could not get key from pool. Created a new instance. "
                + e.getMessage());
            key = new PoolKey();
        }
        if ( username != null && 
             (perUserMaxActive == null 
              || !perUserMaxActive.containsKey(username)) ) 
        {
            username = null;
        }
        key.init(getDataSourceName(), username);
        return key;
    }

    private void returnPoolKey(PoolKey key)
    {
        try
        {
            poolKeyPool.returnObject(key);
        }
        catch (Exception e)
        {
            getLogWriter().println(
                "[WARN] Jdbc2PoolDataSource::returnPoolKey could not return"
                + " key to pool. " + e.getMessage());
        }
    }

    synchronized private void registerInstance()
    {
        if (isNew) 
        {
            int max = 0;
            Iterator i = dsInstanceMap.keySet().iterator();
            while (i.hasNext()) 
            {
                int key = ((Integer)i.next()).intValue();
                max = Math.max(max, key);
            }
            instanceKey = new Integer(max+1);
            FastHashMap fhm = new FastHashMap();
            fhm.setFast(true);
            dsInstanceMap.put(instanceKey, fhm);
            isNew = false;
        }
    }

    synchronized private void registerPool(String username, String password)
         throws javax.naming.NamingException
    {
        Map pools = (Map)dsInstanceMap.get(instanceKey);
        PoolKey key = getPoolKey(username);
        if ( !pools.containsKey(key) ) 
        {
            int maxActive = getDefaultMaxActive();
            int maxIdle = getDefaultMaxIdle();
            int maxWait = getDefaultMaxWait();

            // The source of physical db connections
            ConnectionPoolDataSource cpds = this.cpds;
            if ( cpds == null ) 
            {            
                Context ctx = null;
                if ( jndiEnvironment == null ) 
                {
                    ctx = new InitialContext();                
                }
                else 
                {
                    ctx = new InitialContext(jndiEnvironment);
                }
                cpds = (ConnectionPoolDataSource)ctx.lookup(dataSourceName);
            }

            Object whicheverPool = null;
            if (perUserMaxActive != null 
                && perUserMaxActive.containsKey(username)) 
            {                
                Integer userMax = getPerUserMaxActive(username);
                if ( userMax != null ) 
                {
                    maxActive = userMax.intValue();
                }
                userMax = getPerUserMaxIdle(username);
                if ( userMax != null ) 
                {
                    maxIdle = userMax.intValue();
                }
                userMax = getPerUserMaxWait(username);
                if ( userMax != null ) 
                {
                    maxWait = userMax.intValue();
                }

                // Create an object pool to contain our PooledConnections
                GenericObjectPool pool = new GenericObjectPool(null);
                pool.setMaxActive(maxActive);
                pool.setMaxIdle(maxIdle);
                pool.setMaxWait(maxWait);
                pool.setWhenExhaustedAction(
                    getWhenExhausted(maxActive, maxWait));
                pool.setTestOnBorrow(getTestOnBorrow());
                pool.setTestOnReturn(getTestOnReturn());
                pool.setTimeBetweenEvictionRunsMillis(
                    getTimeBetweenEvictionRunsMillis());
                pool.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
                pool.setMinEvictableIdleTimeMillis(
                    getMinEvictableIdleTimeMillis());
                pool.setTestWhileIdle(getTestWhileIdle());
                
                // Set up the factory we will use (passing the pool associates
                // the factory with the pool, so we do not have to do so
                // explicitly)
                new CPDSConnectionFactory(cpds, pool, validationQuery,
                                          username, password);
                whicheverPool = pool;
            }
            else // use default pool
            {
                // Create an object pool to contain our PooledConnections
                GenericKeyedObjectPool pool = new GenericKeyedObjectPool(null);
                pool.setMaxActive(maxActive);
                pool.setMaxIdle(maxIdle);
                pool.setMaxWait(maxWait);
                pool.setWhenExhaustedAction(
                    getWhenExhausted(maxActive, maxWait));
                pool.setTestOnBorrow(getTestOnBorrow());
                pool.setTestOnReturn(getTestOnReturn());
                pool.setTimeBetweenEvictionRunsMillis(
                    getTimeBetweenEvictionRunsMillis());
                pool.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
                pool.setMinEvictableIdleTimeMillis(
                    getMinEvictableIdleTimeMillis());
                pool.setTestWhileIdle(getTestWhileIdle());
                
                // Set up the factory we will use (passing the pool associates
                // the factory with the pool, so we do not have to do so
                // explicitly)
                new KeyedCPDSConnectionFactory(cpds, pool, validationQuery);
                whicheverPool = pool;
            }
            
            // pools is a FastHashMap set to put the pool in a thread-safe way
            pools.put(key, whicheverPool);
        }        
    }

    private byte getWhenExhausted(int maxActive, int maxWait)
    {
        byte whenExhausted = GenericObjectPool.WHEN_EXHAUSTED_BLOCK;
        if (maxActive <= 0) 
        {
            whenExhausted = GenericObjectPool.WHEN_EXHAUSTED_GROW;
        }
        else if (maxWait == 0) 
        {
            whenExhausted = GenericObjectPool.WHEN_EXHAUSTED_FAIL;
        }
        return whenExhausted;
    }    

    // ----------------------------------------------------------------------
    // Referenceable implementation 

    /**
     * JNDI 内にバインディングするためにオブジェクトを準備する
     * <CODE>Referenceable</CODE> の実装です。
     * {@primary <CODE>Referenceable</CODE> implementation prepares object for
     * binding in jndi.}
     */
    public Reference getReference() 
        throws NamingException
    {
        // this class implements its own factory
        String factory = getClass().getName();
        Reference ref = new Reference(getClass().getName(), factory, null);

        ref.add(new StringRefAddr("isNew", 
                                  String.valueOf(isNew)));
        ref.add(new StringRefAddr("instanceKey", 
            (instanceKey == null ? null : instanceKey.toString()) ));
        ref.add(new StringRefAddr("dataSourceName", getDataSourceName()));
        ref.add(new StringRefAddr("defaultAutoCommit", 
                                  String.valueOf(isDefaultAutoCommit())));
        ref.add(new StringRefAddr("defaultMaxActive", 
                                  String.valueOf(getDefaultMaxActive())));
        ref.add(new StringRefAddr("defaultMaxIdle", 
                                  String.valueOf(getDefaultMaxIdle())));
        ref.add(new StringRefAddr("defaultMaxWait", 
                                  String.valueOf(getDefaultMaxWait())));
        ref.add(new StringRefAddr("defaultReadOnly", 
                                  String.valueOf(isDefaultReadOnly())));
        ref.add(new StringRefAddr("description", getDescription()));

        byte[] ser = null;
        // BinaryRefAddr does not allow null byte[].
        if ( jndiEnvironment != null ) 
        {
            try
            {
                ser = serialize(jndiEnvironment);
                ref.add(new BinaryRefAddr("jndiEnvironment", ser));
            }
            catch (IOException ioe)
            {
                throw new NamingException("An IOException prevented " + 
                   "serializing the jndiEnvironment properties.");
            }
        }

        ref.add(new StringRefAddr("loginTimeout", 
                                  String.valueOf(getLoginTimeout())));

        if ( perUserDefaultAutoCommit != null ) 
        {
            try
            {
                ser = serialize((Serializable)perUserDefaultAutoCommit);
                ref.add(new BinaryRefAddr("perUserDefaultAutoCommit", ser));
            }
            catch (IOException ioe)
            {
                throw new NamingException("An IOException prevented " + 
                   "serializing the perUserDefaultAutoCommit properties.");
            }
        }

        if ( perUserMaxActive != null ) 
        {
            try
            {
                ser = serialize((Serializable)perUserMaxActive);
                ref.add(new BinaryRefAddr("perUserMaxActive", ser));
            }
            catch (IOException ioe)
            {
                throw new NamingException("An IOException prevented " + 
                   "serializing the perUserMaxActive properties.");
            }
        }

        if ( perUserMaxIdle != null ) 
        {
            try
            {
                ser = serialize((Serializable)perUserMaxIdle);
                ref.add(new BinaryRefAddr("perUserMaxIdle", ser));
            }
            catch (IOException ioe)
            {
                throw new NamingException("An IOException prevented " + 
                   "serializing the perUserMaxIdle properties.");
            }
        }

        if ( perUserMaxWait != null ) 
        {
            try
            {
                ser = serialize((Serializable)perUserMaxWait);
                ref.add(new BinaryRefAddr("perUserMaxWait", ser));
            }
            catch (IOException ioe)
            {
                throw new NamingException("An IOException prevented " + 
                   "serializing the perUserMaxWait properties.");
            }
        }

        if ( perUserDefaultReadOnly != null ) 
        {
            try
            {
                ser = serialize((Serializable)perUserDefaultReadOnly);
                ref.add(new BinaryRefAddr("perUserDefaultReadOnly", ser));
            }
            catch (IOException ioe)
            {
                throw new NamingException("An IOException prevented " + 
                   "serializing the perUserDefaultReadOnly properties.");
            }
        }

        ref.add(new StringRefAddr("testOnBorrow", 
                                  String.valueOf(getTestOnBorrow())));
        ref.add(new StringRefAddr("testOnReturn", 
                                  String.valueOf(getTestOnReturn())));
        ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis", 
            String.valueOf(getTimeBetweenEvictionRunsMillis())));
        ref.add(new StringRefAddr("numTestsPerEvictionRun", 
            String.valueOf(getNumTestsPerEvictionRun())));
        ref.add(new StringRefAddr("minEvictableIdleTimeMillis", 
            String.valueOf(getMinEvictableIdleTimeMillis())));
        ref.add(new StringRefAddr("testWhileIdle", 
                                  String.valueOf(getTestWhileIdle())));
        ref.add(new StringRefAddr("validationQuery", getValidationQuery()));
        
        return ref;
    }

    /**
     * オブジェクトを直列化し、バイトの配列に変換します。
     * {@primary Converts a object to a byte array for storage/serialization.}
     *
     * @param obj 直列化の対象
     * {@primary The Serializable to convert.}
     * @return 直列化された byte[]
     * {@primary byte[] with the converted Serializable.}
     * @exception IOException byte[] への変換に失敗した場合
     * {@primary if conversion to a byte[] fails.}
     */
    private static byte[] serialize(Serializable obj)
        throws IOException
    {
        byte[] byteArray = null;
        ByteArrayOutputStream baos = null;
        ObjectOutputStream out = null;
        try
        {
            // These objects are closed in the finally.
            baos = new ByteArrayOutputStream();
            out = new ObjectOutputStream(baos);

            out.writeObject(obj);
            byteArray = baos.toByteArray();
        }
        finally
        {
            if (out != null) 
            {
                out.close();
            }
        }
        return byteArray;
    }


    // ----------------------------------------------------------------------
    // ObjectFactory implementation 

    /**
     * このクラスのインスタンスを生成するための ObjectFactory の実装です。
     * {@primary implements ObjectFactory to create an instance of this class}
     */ 
    public Object getObjectInstance(Object refObj, Name name, 
                                    Context context, Hashtable env) 
        throws Exception 
    {
        // The spec says to return null if we can't create an instance 
        // of the reference
        Jdbc2PoolDataSource ds = null;
        if (refObj instanceof Reference) 
        {
            Reference ref = (Reference)refObj;
	
            if (ref.getClassName().equals(getClass().getName())) 
            {   
                RefAddr ra = ref.get("isNew");
                if (ra != null && ra.getContent() != null) 
                {
                    isNew = Boolean.getBoolean(ra.getContent().toString());
                }

                ra = ref.get("instanceKey");
                if (ra != null && ra.getContent() != null) 
                {
                    instanceKey = new Integer(ra.getContent().toString());
                }

                ra = ref.get("dataSourceName");
                if (ra != null && ra.getContent() != null) 
                {
                    setDataSourceName(ra.getContent().toString());
                }

                ra = ref.get("defaultAutoCommit");
                if (ra != null && ra.getContent() != null) 
                {
                    setDefaultAutoCommit
                        (Boolean.getBoolean(ra.getContent().toString()));
                }

                ra = ref.get("defaultMaxActive");
                if (ra != null && ra.getContent() != null) 
                {
                    setDefaultMaxActive(
                        Integer.parseInt(ra.getContent().toString()));
                }

                ra = ref.get("defaultMaxIdle");
                if (ra != null && ra.getContent() != null) 
                {
                    setDefaultMaxIdle(
                        Integer.parseInt(ra.getContent().toString()));
                }

                ra = ref.get("defaultMaxWait");
                if (ra != null && ra.getContent() != null) 
                {
                    setDefaultMaxWait(
                        Integer.parseInt(ra.getContent().toString()));
                }

                ra = ref.get("defaultReadOnly");
                if (ra != null && ra.getContent() != null) 
                {
                    setDefaultReadOnly
                        (Boolean.getBoolean(ra.getContent().toString()));
                }

                ra = ref.get("description");
                if (ra != null && ra.getContent() != null) 
                {
                    setDescription(ra.getContent().toString());
                }

                ra = ref.get("jndiEnvironment");
                if (ra != null  && ra.getContent() != null) 
                {
                    byte[] serialized = (byte[])ra.getContent();
                    jndiEnvironment = 
                        (Properties)SerializationUtils.deserialize(serialized);
                }
                
                ra = ref.get("loginTimeout");
                if (ra != null && ra.getContent() != null) 
                {
                    setLoginTimeout(
                        Integer.parseInt(ra.getContent().toString()));
                }

                ra = ref.get("perUserDefaultAutoCommit");
                if (ra != null  && ra.getContent() != null) 
                {
                    byte[] serialized = (byte[])ra.getContent();
                    perUserDefaultAutoCommit = 
                        (Map)SerializationUtils.deserialize(serialized);
                }
                
                ra = ref.get("perUserMaxActive");
                if (ra != null  && ra.getContent() != null) 
                {
                    byte[] serialized = (byte[])ra.getContent();
                    perUserMaxActive = 
                        (Map)SerializationUtils.deserialize(serialized);
                }

                ra = ref.get("perUserMaxIdle");
                if (ra != null  && ra.getContent() != null) 
                {
                    byte[] serialized = (byte[])ra.getContent();
                    perUserMaxIdle = 
                        (Map)SerializationUtils.deserialize(serialized);
                }

                ra = ref.get("perUserMaxWait");
                if (ra != null  && ra.getContent() != null) 
                {
                    byte[] serialized = (byte[])ra.getContent();
                    perUserMaxWait = 
                        (Map)SerializationUtils.deserialize(serialized);
                }
                
                ra = ref.get("perUserDefaultReadOnly");
                if (ra != null  && ra.getContent() != null) 
                {
                    byte[] serialized = (byte[])ra.getContent();
                    perUserDefaultReadOnly = 
                        (Map)SerializationUtils.deserialize(serialized);
                }
                
                ra = ref.get("testOnBorrow");
                if (ra != null && ra.getContent() != null) 
                {
                    setTestOnBorrow
                        (Boolean.getBoolean(ra.getContent().toString()));
                }

                ra = ref.get("testOnReturn");
                if (ra != null && ra.getContent() != null) 
                {
                    setTestOnReturn
                        (Boolean.getBoolean(ra.getContent().toString()));
                }

                ra = ref.get("timeBetweenEvictionRunsMillis");
                if (ra != null && ra.getContent() != null) 
                {
                    setTimeBetweenEvictionRunsMillis(
                        Integer.parseInt(ra.getContent().toString()));
                }

                ra = ref.get("numTestsPerEvictionRun");
                if (ra != null && ra.getContent() != null) 
                {
                    setNumTestsPerEvictionRun(
                        Integer.parseInt(ra.getContent().toString()));
                }

                ra = ref.get("minEvictableIdleTimeMillis");
                if (ra != null && ra.getContent() != null) 
                {
                    setMinEvictableIdleTimeMillis(
                        Integer.parseInt(ra.getContent().toString()));
                }

                ra = ref.get("testWhileIdle");
                if (ra != null && ra.getContent() != null) 
                {
                    setTestWhileIdle
                        (Boolean.getBoolean(ra.getContent().toString()));
                }
                
                ra = ref.get("validationQuery");
                if (ra != null && ra.getContent() != null) 
                {
                    setValidationQuery(ra.getContent().toString());
                }

                ds = this;
            }            
        }
        
        return ds;
    }
}

