    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.dbcp2.datasources;
    019
    020import java.io.OutputStreamWriter;
    021import java.io.PrintWriter;
    022import java.io.Serializable;
    023import java.nio.charset.StandardCharsets;
    024import java.sql.Connection;
    025import java.sql.SQLException;
    026import java.sql.SQLFeatureNotSupportedException;
    027import java.util.NoSuchElementException;
    028import java.util.Properties;
    029import java.util.logging.Logger;
    030
    031import javax.naming.Context;
    032import javax.naming.InitialContext;
    033import javax.naming.Referenceable;
    034import javax.sql.ConnectionPoolDataSource;
    035import javax.sql.DataSource;
    036import javax.sql.PooledConnection;
    037
    038import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
    039import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
    040
    041/**
    042 * <p>The base class for <code>SharedPoolDataSource</code> and
    043 * <code>PerUserPoolDataSource</code>.  Many of the configuration properties
    044 * are shared and defined here.  This class is declared public in order
    045 * to allow particular usage with commons-beanutils; do not make direct
    046 * use of it outside of <em>commons-dbcp2</em>.
    047 * </p>
    048 *
    049 * <p>
    050 * A J2EE container will normally provide some method of initializing the
    051 * <code>DataSource</code> whose attributes are presented
    052 * as bean getters/setters and then deploying it via JNDI.  It is then
    053 * available to an application as a source of pooled logical connections to
    054 * the database.  The pool needs a source of physical connections.  This
    055 * source is in the form of a <code>ConnectionPoolDataSource</code> that
    056 * can be specified via the {@link #setDataSourceName(String)} used to
    057 * lookup the source via JNDI.
    058 * </p>
    059 *
    060 * <p>
    061 * Although normally used within a JNDI environment, A DataSource
    062 * can be instantiated and initialized as any bean.  In this case the
    063 * <code>ConnectionPoolDataSource</code> will likely be instantiated in
    064 * a similar manner.  This class allows the physical source of connections
    065 * to be attached directly to this pool using the
    066 * {@link #setConnectionPoolDataSource(ConnectionPoolDataSource)} method.
    067 * </p>
    068 *
    069 * <p>
    070 * The dbcp package contains an adapter,
    071 * {@link org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS},
    072 * that can be used to allow the use of <code>DataSource</code>'s based on this
    073 * class with JDBC driver implementations that do not supply a
    074 * <code>ConnectionPoolDataSource</code>, but still
    075 * provide a {@link java.sql.Driver} implementation.
    076 * </p>
    077 *
    078 * <p>
    079 * The <a href="package-summary.html">package documentation</a> contains an
    080 * example using Apache Tomcat and JNDI and it also contains a non-JNDI example.
    081 * </p>
    082 *
    083 * @author John D. McNally
    084 * @since 2.0
    085 */
    086public abstract class InstanceKeyDataSource
    087        implements DataSource, Referenceable, Serializable, AutoCloseable {
    088
    089    private static final long serialVersionUID = -6819270431752240878L;
    090
    091    private static final String GET_CONNECTION_CALLED
    092            = "A Connection was already requested from this source, "
    093            + "further initialization is not allowed.";
    094    private static final String BAD_TRANSACTION_ISOLATION
    095        = "The requested TransactionIsolation level is invalid.";
    096
    097    /**
    098    * Internal constant to indicate the level is not set.
    099    */
    100    protected static final int UNKNOWN_TRANSACTIONISOLATION = -1;
    101
    102    /** Guards property setters - once true, setters throw IllegalStateException */
    103    private volatile boolean getConnectionCalled = false;
    104
    105    /** Underlying source of PooledConnections */
    106    private ConnectionPoolDataSource dataSource = null;
    107
    108    /** DataSource Name used to find the ConnectionPoolDataSource */
    109    private String dataSourceName = null;
    110
    111    /** Description */
    112    private String description = null;
    113
    114    /** Environment that may be used to set up a jndi initial context. */
    115    private Properties jndiEnvironment = null;
    116
    117    /** Login TimeOut in seconds */
    118    private int loginTimeout = 0;
    119
    120    /** Log stream */
    121    private PrintWriter logWriter = null;
    122
    123    /** Instance key */
    124    private String instanceKey = null;
    125
    126    // Pool properties
    127    private boolean defaultBlockWhenExhausted =
    128            BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED;
    129    private String defaultEvictionPolicyClassName =
    130            BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME;
    131    private boolean defaultLifo = BaseObjectPoolConfig.DEFAULT_LIFO;
    132    private int defaultMaxIdle =
    133            GenericKeyedObjectPoolConfig.DEFAULT_MAX_IDLE_PER_KEY;
    134    private int defaultMaxTotal =
    135            GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
    136    private long defaultMaxWaitMillis =
    137            BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS;
    138    private long defaultMinEvictableIdleTimeMillis =
    139            BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
    140    private int defaultMinIdle =
    141            GenericKeyedObjectPoolConfig.DEFAULT_MIN_IDLE_PER_KEY;
    142    private int defaultNumTestsPerEvictionRun =
    143            BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
    144    private long defaultSoftMinEvictableIdleTimeMillis =
    145            BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
    146    private boolean defaultTestOnCreate =
    147            BaseObjectPoolConfig.DEFAULT_TEST_ON_CREATE;
    148    private boolean defaultTestOnBorrow =
    149            BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW;
    150    private boolean defaultTestOnReturn =
    151            BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN;
    152    private boolean defaultTestWhileIdle =
    153            BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE;
    154    private long defaultTimeBetweenEvictionRunsMillis =
    155            BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
    156
    157    // Connection factory properties
    158    private String validationQuery = null;
    159    private int validationQueryTimeout = -1;
    160    private boolean rollbackAfterValidation = false;
    161    private long maxConnLifetimeMillis = -1;
    162
    163    // Connection properties
    164    private Boolean defaultAutoCommit = null;
    165    private int defaultTransactionIsolation = UNKNOWN_TRANSACTIONISOLATION;
    166    private Boolean defaultReadOnly = null;
    167
    168
    169    /**
    170     * Default no-arg constructor for Serialization
    171     */
    172    public InstanceKeyDataSource() {
    173    }
    174
    175    /**
    176     * Throws an IllegalStateException, if a PooledConnection has already
    177     * been requested.
    178     */
    179    protected void assertInitializationAllowed()
    180        throws IllegalStateException {
    181        if (getConnectionCalled) {
    182            throw new IllegalStateException(GET_CONNECTION_CALLED);
    183        }
    184    }
    185
    186    /**
    187     * Close the connection pool being maintained by this datasource.
    188     */
    189    @Override
    190    public abstract void close() throws Exception;
    191
    192    protected abstract PooledConnectionManager getConnectionManager(UserPassKey upkey);
    193
    194    /* JDBC_4_ANT_KEY_BEGIN */
    195    @Override
    196    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
    197        return false;
    198    }
    199
    200    @Override
    201    public <T> T unwrap(final Class<T> iface) throws SQLException {
    202        throw new SQLException("InstanceKeyDataSource is not a wrapper.");
    203    }
    204    /* JDBC_4_ANT_KEY_END */
    205
    206    @Override
    207    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    208        throw new SQLFeatureNotSupportedException();
    209    }
    210
    211
    212    // -------------------------------------------------------------------
    213    // Properties
    214
    215    /**
    216     * Gets the default value for
    217     * {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per
    218     * user pool.
    219     */
    220    public boolean getDefaultBlockWhenExhausted() {
    221        return this.defaultBlockWhenExhausted;
    222    }
    223
    224    /**
    225     * Sets the default value for
    226     * {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per
    227     * user pool.
    228     */
    229    public void setDefaultBlockWhenExhausted(final boolean blockWhenExhausted) {
    230        assertInitializationAllowed();
    231        this.defaultBlockWhenExhausted = blockWhenExhausted;
    232    }
    233
    234    /**
    235     * Gets the default value for
    236     * {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for
    237     * each per user pool.
    238     */
    239    public String getDefaultEvictionPolicyClassName() {
    240        return this.defaultEvictionPolicyClassName;
    241    }
    242
    243    /**
    244     * Sets the default value for
    245     * {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for
    246     * each per user pool.
    247     */
    248    public void setDefaultEvictionPolicyClassName(
    249            final String evictionPolicyClassName) {
    250        assertInitializationAllowed();
    251        this.defaultEvictionPolicyClassName = evictionPolicyClassName;
    252    }
    253
    254    /**
    255     * Gets the default value for
    256     * {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
    257     */
    258    public boolean getDefaultLifo() {
    259        return this.defaultLifo;
    260    }
    261
    262    /**
    263     * Sets the default value for
    264     * {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
    265     */
    266    public void setDefaultLifo(final boolean lifo) {
    267        assertInitializationAllowed();
    268        this.defaultLifo = lifo;
    269    }
    270
    271    /**
    272     * Gets the default value for
    273     * {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user
    274     * pool.
    275     */
    276    public int getDefaultMaxIdle() {
    277        return this.defaultMaxIdle;
    278    }
    279
    280    /**
    281     * Sets the default value for
    282     * {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user
    283     * pool.
    284     */
    285    public void setDefaultMaxIdle(final int maxIdle) {
    286        assertInitializationAllowed();
    287        this.defaultMaxIdle = maxIdle;
    288    }
    289
    290    /**
    291     * Gets the default value for
    292     * {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per
    293     * user pool.
    294     */
    295    public int getDefaultMaxTotal() {
    296        return this.defaultMaxTotal;
    297    }
    298
    299    /**
    300     * Sets the default value for
    301     * {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per
    302     * user pool.
    303     */
    304    public void setDefaultMaxTotal(final int maxTotal) {
    305        assertInitializationAllowed();
    306        this.defaultMaxTotal = maxTotal;
    307    }
    308
    309    /**
    310     * Gets the default value for
    311     * {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user
    312     * pool.
    313     */
    314    public long getDefaultMaxWaitMillis() {
    315        return this.defaultMaxWaitMillis;
    316    }
    317
    318    /**
    319     * Sets the default value for
    320     * {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user
    321     * pool.
    322     */
    323    public void setDefaultMaxWaitMillis(final long maxWaitMillis) {
    324        assertInitializationAllowed();
    325        this.defaultMaxWaitMillis = maxWaitMillis;
    326    }
    327
    328    /**
    329     * Gets the default value for
    330     * {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleTimeMillis()} for
    331     * each per user pool.
    332     */
    333    public long getDefaultMinEvictableIdleTimeMillis() {
    334        return this.defaultMinEvictableIdleTimeMillis;
    335    }
    336
    337    /**
    338     * Sets the default value for
    339     * {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleTimeMillis()} for
    340     * each per user pool.
    341     */
    342    public void setDefaultMinEvictableIdleTimeMillis(
    343            final long minEvictableIdleTimeMillis) {
    344        assertInitializationAllowed();
    345        this.defaultMinEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    346    }
    347
    348    /**
    349     * Gets the default value for
    350     * {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user
    351     * pool.
    352     */
    353    public int getDefaultMinIdle() {
    354        return this.defaultMinIdle;
    355    }
    356
    357    /**
    358     * Sets the default value for
    359     * {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user
    360     * pool.
    361     */
    362    public void setDefaultMinIdle(final int minIdle) {
    363        assertInitializationAllowed();
    364        this.defaultMinIdle = minIdle;
    365    }
    366
    367    /**
    368     * Gets the default value for
    369     * {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each
    370     * per user pool.
    371     */
    372    public int getDefaultNumTestsPerEvictionRun() {
    373        return this.defaultNumTestsPerEvictionRun;
    374    }
    375
    376    /**
    377     * Sets the default value for
    378     * {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each
    379     * per user pool.
    380     */
    381    public void setDefaultNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
    382        assertInitializationAllowed();
    383        this.defaultNumTestsPerEvictionRun = numTestsPerEvictionRun;
    384    }
    385
    386    /**
    387     * Gets the default value for
    388     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each
    389     * per user pool.
    390     */
    391    public long getDefaultSoftMinEvictableIdleTimeMillis() {
    392        return this.defaultSoftMinEvictableIdleTimeMillis;
    393    }
    394
    395    /**
    396     * Sets the default value for
    397     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
    398     */
    399    public void setDefaultSoftMinEvictableIdleTimeMillis(
    400            final long softMinEvictableIdleTimeMillis) {
    401        assertInitializationAllowed();
    402        this.defaultSoftMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis;
    403    }
    404
    405    /**
    406     * Gets the default value for
    407     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getTestOnCreate()} for each per user pool.
    408     */
    409    public boolean getDefaultTestOnCreate() {
    410        return this.defaultTestOnCreate;
    411    }
    412
    413    /**
    414     * Sets the default value for
    415     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getTestOnCreate()} for each per user pool.
    416     */
    417    public void setDefaultTestOnCreate(final boolean testOnCreate) {
    418        assertInitializationAllowed();
    419        this.defaultTestOnCreate = testOnCreate;
    420    }
    421
    422    /**
    423     * Gets the default value for
    424     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getTestOnBorrow()} for each per user pool.
    425     */
    426    public boolean getDefaultTestOnBorrow() {
    427        return this.defaultTestOnBorrow;
    428    }
    429
    430    /**
    431     * Sets the default value for
    432     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getTestOnBorrow()} for each per user pool.
    433     */
    434    public void setDefaultTestOnBorrow(final boolean testOnBorrow) {
    435        assertInitializationAllowed();
    436        this.defaultTestOnBorrow = testOnBorrow;
    437    }
    438
    439    /**
    440     * Gets the default value for
    441     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getTestOnReturn()} for each per user pool.
    442     */
    443    public boolean getDefaultTestOnReturn() {
    444        return this.defaultTestOnReturn;
    445    }
    446
    447    /**
    448     * Sets the default value for
    449     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getTestOnReturn()} for each per user pool.
    450     */
    451    public void setDefaultTestOnReturn(final boolean testOnReturn) {
    452        assertInitializationAllowed();
    453        this.defaultTestOnReturn = testOnReturn;
    454    }
    455
    456    /**
    457     * Gets the default value for
    458     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getTestWhileIdle()} for each per user pool.
    459     */
    460    public boolean getDefaultTestWhileIdle() {
    461        return this.defaultTestWhileIdle;
    462    }
    463
    464    /**
    465     * Sets the default value for
    466     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getTestWhileIdle()} for each per user pool.
    467     */
    468    public void setDefaultTestWhileIdle(final boolean testWhileIdle) {
    469        assertInitializationAllowed();
    470        this.defaultTestWhileIdle = testWhileIdle;
    471    }
    472
    473    /**
    474     * Gets the default value for
    475     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getTimeBetweenEvictionRunsMillis ()} for each
    476     * per user pool.
    477     */
    478    public long getDefaultTimeBetweenEvictionRunsMillis () {
    479        return this.defaultTimeBetweenEvictionRunsMillis ;
    480    }
    481
    482    /**
    483     * Sets the default value for
    484     * {@link org.apache.commons.pool2.impl.GenericObjectPool GenericObjectPool#getTimeBetweenEvictionRunsMillis ()} for each
    485     * per user pool.
    486     */
    487    public void setDefaultTimeBetweenEvictionRunsMillis (
    488            final long timeBetweenEvictionRunsMillis ) {
    489        assertInitializationAllowed();
    490        this.defaultTimeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis ;
    491    }
    492
    493    /**
    494     * Get the value of connectionPoolDataSource.  This method will return
    495     * null, if the backing datasource is being accessed via jndi.
    496     *
    497     * @return value of connectionPoolDataSource.
    498     */
    499    public ConnectionPoolDataSource getConnectionPoolDataSource() {
    500        return dataSource;
    501    }
    502
    503    /**
    504     * Set the backend ConnectionPoolDataSource.  This property should not be
    505     * set if using jndi to access the datasource.
    506     *
    507     * @param v  Value to assign to connectionPoolDataSource.
    508     */
    509    public void setConnectionPoolDataSource(final ConnectionPoolDataSource v) {
    510        assertInitializationAllowed();
    511        if (dataSourceName != null) {
    512            throw new IllegalStateException(
    513                "Cannot set the DataSource, if JNDI is used.");
    514        }
    515        if (dataSource != null)
    516        {
    517            throw new IllegalStateException(
    518                "The CPDS has already been set. It cannot be altered.");
    519        }
    520        dataSource = v;
    521        instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this);
    522    }
    523
    524    /**
    525     * Get the name of the ConnectionPoolDataSource which backs this pool.
    526     * This name is used to look up the datasource from a jndi service
    527     * provider.
    528     *
    529     * @return value of dataSourceName.
    530     */
    531    public String getDataSourceName() {
    532        return dataSourceName;
    533    }
    534
    535    /**
    536     * Set the name of the ConnectionPoolDataSource which backs this pool.
    537     * This name is used to look up the datasource from a jndi service
    538     * provider.
    539     *
    540     * @param v  Value to assign to dataSourceName.
    541     */
    542    public void setDataSourceName(final String v) {
    543        assertInitializationAllowed();
    544        if (dataSource != null) {
    545            throw new IllegalStateException(
    546                "Cannot set the JNDI name for the DataSource, if already " +
    547                "set using setConnectionPoolDataSource.");
    548        }
    549        if (dataSourceName != null)
    550        {
    551            throw new IllegalStateException(
    552                "The DataSourceName has already been set. " +
    553                "It cannot be altered.");
    554        }
    555        this.dataSourceName = v;
    556        instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this);
    557    }
    558
    559    /**
    560     * Get the value of defaultAutoCommit, which defines the state of
    561     * connections handed out from this pool.  The value can be changed
    562     * on the Connection using Connection.setAutoCommit(boolean).
    563     * The default is <code>null</code> which will use the default value for the
    564     * drive.
    565     *
    566     * @return value of defaultAutoCommit.
    567     */
    568    public Boolean isDefaultAutoCommit() {
    569        return defaultAutoCommit;
    570    }
    571
    572    /**
    573     * Set the value of defaultAutoCommit, which defines the state of
    574     * connections handed out from this pool.  The value can be changed
    575     * on the Connection using Connection.setAutoCommit(boolean).
    576     * The default is <code>null</code> which will use the default value for the
    577     * drive.
    578     *
    579     * @param v  Value to assign to defaultAutoCommit.
    580     */
    581    public void setDefaultAutoCommit(final Boolean v) {
    582        assertInitializationAllowed();
    583        this.defaultAutoCommit = v;
    584    }
    585
    586    /**
    587     * Get the value of defaultReadOnly, which defines the state of
    588     * connections handed out from this pool.  The value can be changed
    589     * on the Connection using Connection.setReadOnly(boolean).
    590     * The default is <code>null</code> which will use the default value for the
    591     * drive.
    592     *
    593     * @return value of defaultReadOnly.
    594     */
    595    public Boolean isDefaultReadOnly() {
    596        return defaultReadOnly;
    597    }
    598
    599    /**
    600     * Set the value of defaultReadOnly, which defines the state of
    601     * connections handed out from this pool.  The value can be changed
    602     * on the Connection using Connection.setReadOnly(boolean).
    603     * The default is <code>null</code> which will use the default value for the
    604     * drive.
    605     *
    606     * @param v  Value to assign to defaultReadOnly.
    607     */
    608    public void setDefaultReadOnly(final Boolean v) {
    609        assertInitializationAllowed();
    610        this.defaultReadOnly = v;
    611    }
    612
    613    /**
    614     * Get the value of defaultTransactionIsolation, which defines the state of
    615     * connections handed out from this pool.  The value can be changed
    616     * on the Connection using Connection.setTransactionIsolation(int).
    617     * If this method returns -1, the default is JDBC driver dependent.
    618     *
    619     * @return value of defaultTransactionIsolation.
    620     */
    621    public int getDefaultTransactionIsolation() {
    622        return defaultTransactionIsolation;
    623    }
    624
    625    /**
    626     * Set the value of defaultTransactionIsolation, which defines the state of
    627     * connections handed out from this pool.  The value can be changed
    628     * on the Connection using Connection.setTransactionIsolation(int).
    629     * The default is JDBC driver dependent.
    630     *
    631     * @param v  Value to assign to defaultTransactionIsolation
    632     */
    633    public void setDefaultTransactionIsolation(final int v) {
    634        assertInitializationAllowed();
    635        switch (v) {
    636        case Connection.TRANSACTION_NONE:
    637        case Connection.TRANSACTION_READ_COMMITTED:
    638        case Connection.TRANSACTION_READ_UNCOMMITTED:
    639        case Connection.TRANSACTION_REPEATABLE_READ:
    640        case Connection.TRANSACTION_SERIALIZABLE:
    641            break;
    642        default:
    643            throw new IllegalArgumentException(BAD_TRANSACTION_ISOLATION);
    644        }
    645        this.defaultTransactionIsolation = v;
    646    }
    647
    648    /**
    649     * Get the description.  This property is defined by JDBC as for use with
    650     * GUI (or other) tools that might deploy the datasource.  It serves no
    651     * internal purpose.
    652     *
    653     * @return value of description.
    654     */
    655    public String getDescription() {
    656        return description;
    657    }
    658
    659    /**
    660     * Set the description.  This property is defined by JDBC as for use with
    661     * GUI (or other) tools that might deploy the datasource.  It serves no
    662     * internal purpose.
    663     *
    664     * @param v  Value to assign to description.
    665     */
    666    public void setDescription(final String v) {
    667        this.description = v;
    668    }
    669
    670    protected String getInstanceKey() {
    671        return instanceKey;
    672    }
    673
    674    /**
    675     * Get the value of jndiEnvironment which is used when instantiating
    676     * a jndi InitialContext.  This InitialContext is used to locate the
    677     * backend ConnectionPoolDataSource.
    678     *
    679     * @return value of jndiEnvironment.
    680     */
    681    public String getJndiEnvironment(final String key) {
    682        String value = null;
    683        if (jndiEnvironment != null) {
    684            value = jndiEnvironment.getProperty(key);
    685        }
    686        return value;
    687    }
    688
    689    /**
    690     * Sets the value of the given JNDI environment property to be used when
    691     * instantiating a JNDI InitialContext. This InitialContext is used to
    692     * locate the backend ConnectionPoolDataSource.
    693     *
    694     * @param key the JNDI environment property to set.
    695     * @param value the value assigned to specified JNDI environment property.
    696     */
    697    public void setJndiEnvironment(final String key, final String value) {
    698        if (jndiEnvironment == null) {
    699            jndiEnvironment = new Properties();
    700        }
    701        jndiEnvironment.setProperty(key, value);
    702    }
    703
    704    /**
    705     * Sets the JNDI environment to be used when instantiating a JNDI
    706     * InitialContext. This InitialContext is used to locate the backend
    707     * ConnectionPoolDataSource.
    708     *
    709     * @param properties the JNDI environment property to set which will
    710     *                   overwrite any current settings
    711     */
    712    void setJndiEnvironment(final Properties properties) {
    713        if (jndiEnvironment == null) {
    714            jndiEnvironment = new Properties();
    715        } else {
    716            jndiEnvironment.clear();
    717        }
    718        jndiEnvironment.putAll(properties);
    719    }
    720
    721    /**
    722     * Get the value of loginTimeout.
    723     * @return value of loginTimeout.
    724     */
    725    @Override
    726    public int getLoginTimeout() {
    727        return loginTimeout;
    728    }
    729
    730    /**
    731     * Set the value of loginTimeout.
    732     * @param v  Value to assign to loginTimeout.
    733     */
    734    @Override
    735    public void setLoginTimeout(final int v) {
    736        this.loginTimeout = v;
    737    }
    738
    739    /**
    740     * Get the value of logWriter.
    741     * @return value of logWriter.
    742     */
    743    @Override
    744    public PrintWriter getLogWriter() {
    745        if (logWriter == null) {
    746            logWriter = new PrintWriter(
    747                    new OutputStreamWriter(System.out, StandardCharsets.UTF_8));
    748        }
    749        return logWriter;
    750    }
    751
    752    /**
    753     * Set the value of logWriter.
    754     * @param v  Value to assign to logWriter.
    755     */
    756    @Override
    757    public void setLogWriter(final PrintWriter v) {
    758        this.logWriter = v;
    759    }
    760
    761    /**
    762     * The SQL query that will be used to validate connections from this pool
    763     * before returning them to the caller.  If specified, this query
    764     * <strong>MUST</strong> be an SQL SELECT statement that returns at least
    765     * one row. If not specified, {@link Connection#isValid(int)} will be used
    766     * to validate connections.
    767     */
    768    public String getValidationQuery() {
    769        return this.validationQuery;
    770    }
    771
    772    /**
    773     * The SQL query that will be used to validate connections from this pool
    774     * before returning them to the caller.  If specified, this query
    775     * <strong>MUST</strong> be an SQL SELECT statement that returns at least
    776     * one row. If not specified, connections will be validated using
    777     * {@link Connection#isValid(int)}.
    778     */
    779    public void setValidationQuery(final String validationQuery) {
    780        assertInitializationAllowed();
    781        this.validationQuery = validationQuery;
    782    }
    783
    784    /**
    785     * Returns the timeout in seconds before the validation query fails.
    786     */
    787    public int getValidationQueryTimeout() {
    788        return validationQueryTimeout;
    789    }
    790
    791    /**
    792     * Sets the timeout in seconds before the validation query fails.
    793     *
    794     * @param validationQueryTimeout    The new timeout in seconds
    795     */
    796    public void setValidationQueryTimeout(final int validationQueryTimeout) {
    797        this.validationQueryTimeout = validationQueryTimeout;
    798    }
    799
    800    /**
    801     * Whether a rollback will be issued after executing the SQL query
    802     * that will be used to validate connections from this pool
    803     * before returning them to the caller.
    804     *
    805     * @return true if a rollback will be issued after executing the
    806     * validation query
    807     */
    808    public boolean isRollbackAfterValidation() {
    809        return this.rollbackAfterValidation;
    810    }
    811
    812    /**
    813     * Whether a rollback will be issued after executing the SQL query
    814     * that will be used to validate connections from this pool
    815     * before returning them to the caller. Default behavior is NOT
    816     * to issue a rollback. The setting will only have an effect
    817     * if a validation query is set
    818     *
    819     * @param rollbackAfterValidation new property value
    820     */
    821    public void setRollbackAfterValidation(final boolean rollbackAfterValidation) {
    822        assertInitializationAllowed();
    823        this.rollbackAfterValidation = rollbackAfterValidation;
    824    }
    825
    826    /**
    827     * Returns the maximum permitted lifetime of a connection in milliseconds. A
    828     * value of zero or less indicates an infinite lifetime.
    829     */
    830    public long getMaxConnLifetimeMillis() {
    831        return maxConnLifetimeMillis;
    832    }
    833
    834    /**
    835     * <p>Sets the maximum permitted lifetime of a connection in
    836     * milliseconds. A value of zero or less indicates an infinite lifetime.</p>
    837     * <p>
    838     * Note: this method currently has no effect once the pool has been
    839     * initialized.  The pool is initialized the first time one of the
    840     * following methods is invoked: <code>getConnection, setLogwriter,
    841     * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p>
    842     */
    843    public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
    844        this.maxConnLifetimeMillis = maxConnLifetimeMillis;
    845    }
    846
    847    // ----------------------------------------------------------------------
    848    // Instrumentation Methods
    849
    850    // ----------------------------------------------------------------------
    851    // DataSource implementation
    852
    853    /**
    854     * Attempt to establish a database connection.
    855     */
    856    @Override
    857    public Connection getConnection() throws SQLException {
    858        return getConnection(null, null);
    859    }
    860
    861    /**
    862     * Attempt to retrieve a database connection using {@link #getPooledConnectionAndInfo(String, String)}
    863     * with the provided username and password.  The password on the {@link PooledConnectionAndInfo}
    864     * instance returned by <code>getPooledConnectionAndInfo</code> is compared to the <code>password</code>
    865     * parameter.  If the comparison fails, a database connection using the supplied username and password
    866     * is attempted.  If the connection attempt fails, an SQLException is thrown, indicating that the given password
    867     * did not match the password used to create the pooled connection.  If the connection attempt succeeds, this
    868     * means that the database password has been changed.  In this case, the <code>PooledConnectionAndInfo</code>
    869     * instance retrieved with the old password is destroyed and the <code>getPooledConnectionAndInfo</code> is
    870     * repeatedly invoked until a <code>PooledConnectionAndInfo</code> instance with the new password is returned.
    871     *
    872     */
    873    @Override
    874    public Connection getConnection(final String username, final String password)
    875            throws SQLException {
    876        if (instanceKey == null) {
    877            throw new SQLException("Must set the ConnectionPoolDataSource "
    878                    + "through setDataSourceName or setConnectionPoolDataSource"
    879                    + " before calling getConnection.");
    880        }
    881        getConnectionCalled = true;
    882        PooledConnectionAndInfo info = null;
    883        try {
    884            info = getPooledConnectionAndInfo(username, password);
    885        } catch (final NoSuchElementException e) {
    886            closeDueToException(info);
    887            throw new SQLException("Cannot borrow connection from pool", e);
    888        } catch (final RuntimeException e) {
    889            closeDueToException(info);
    890            throw e;
    891        } catch (final SQLException e) {
    892            closeDueToException(info);
    893            throw e;
    894        } catch (final Exception e) {
    895            closeDueToException(info);
    896            throw new SQLException("Cannot borrow connection from pool", e);
    897        }
    898
    899        if (!(null == password ? null == info.getPassword()
    900                : password.equals(info.getPassword()))) {  // Password on PooledConnectionAndInfo does not match
    901            try { // See if password has changed by attempting connection
    902                testCPDS(username, password);
    903            } catch (final SQLException ex) {
    904                // Password has not changed, so refuse client, but return connection to the pool
    905                closeDueToException(info);
    906                throw new SQLException("Given password did not match password used"
    907                                       + " to create the PooledConnection.", ex);
    908            } catch (final javax.naming.NamingException ne) {
    909                throw new SQLException(
    910                        "NamingException encountered connecting to database", ne);
    911            }
    912            /*
    913             * Password must have changed -> destroy connection and keep retrying until we get a new, good one,
    914             * destroying any idle connections with the old password as we pull them from the pool.
    915             */
    916            final UserPassKey upkey = info.getUserPassKey();
    917            final PooledConnectionManager manager = getConnectionManager(upkey);
    918            manager.invalidate(info.getPooledConnection()); // Destroy and remove from pool
    919            manager.setPassword(upkey.getPassword()); // Reset the password on the factory if using CPDSConnectionFactory
    920            info = null;
    921            for (int i = 0; i < 10; i++) { // Bound the number of retries - only needed if bad instances return
    922                try {
    923                    info = getPooledConnectionAndInfo(username, password);
    924                } catch (final NoSuchElementException e) {
    925                    closeDueToException(info);
    926                    throw new SQLException("Cannot borrow connection from pool", e);
    927                } catch (final RuntimeException e) {
    928                    closeDueToException(info);
    929                    throw e;
    930                } catch (final SQLException e) {
    931                    closeDueToException(info);
    932                    throw e;
    933                } catch (final Exception e) {
    934                    closeDueToException(info);
    935                    throw new SQLException("Cannot borrow connection from pool", e);
    936                }
    937                if (info != null && password != null && password.equals(info.getPassword())) {
    938                    break;
    939                }
    940                if (info != null) {
    941                    manager.invalidate(info.getPooledConnection());
    942                }
    943                info = null;
    944            }
    945            if (info == null) {
    946                throw new SQLException("Cannot borrow connection from pool - password change failure.");
    947            }
    948        }
    949
    950        final Connection con = info.getPooledConnection().getConnection();
    951        try {
    952            setupDefaults(con, username);
    953            con.clearWarnings();
    954            return con;
    955        } catch (final SQLException ex) {
    956            try {
    957                con.close();
    958            } catch (final Exception exc) {
    959                getLogWriter().println(
    960                     "ignoring exception during close: " + exc);
    961            }
    962            throw ex;
    963        }
    964    }
    965
    966    protected abstract PooledConnectionAndInfo
    967        getPooledConnectionAndInfo(String username, String password)
    968        throws SQLException;
    969
    970    protected abstract void setupDefaults(Connection con, String username)
    971        throws SQLException;
    972
    973
    974    private void closeDueToException(final PooledConnectionAndInfo info) {
    975        if (info != null) {
    976            try {
    977                info.getPooledConnection().getConnection().close();
    978            } catch (final Exception e) {
    979                // do not throw this exception because we are in the middle
    980                // of handling another exception.  But record it because
    981                // it potentially leaks connections from the pool.
    982                getLogWriter().println("[ERROR] Could not return connection to "
    983                    + "pool during exception handling. " + e.getMessage());
    984            }
    985        }
    986    }
    987
    988    protected ConnectionPoolDataSource
    989        testCPDS(final String username, final String password)
    990        throws javax.naming.NamingException, SQLException {
    991        // The source of physical db connections
    992        ConnectionPoolDataSource cpds = this.dataSource;
    993        if (cpds == null) {
    994            Context ctx = null;
    995            if (jndiEnvironment == null) {
    996                ctx = new InitialContext();
    997            } else {
    998                ctx = new InitialContext(jndiEnvironment);
    999            }
    1000            final Object ds = ctx.lookup(dataSourceName);
    1001            if (ds instanceof ConnectionPoolDataSource) {
    1002                cpds = (ConnectionPoolDataSource) ds;
    1003            } else {
    1004                throw new SQLException("Illegal configuration: "
    1005                    + "DataSource " + dataSourceName
    1006                    + " (" + ds.getClass().getName() + ")"
    1007                    + " doesn't implement javax.sql.ConnectionPoolDataSource");
    1008            }
    1009        }
    1010
    1011        // try to get a connection with the supplied username/password
    1012        PooledConnection conn = null;
    1013        try {
    1014            if (username != null) {
    1015                conn = cpds.getPooledConnection(username, password);
    1016            }
    1017            else {
    1018                conn = cpds.getPooledConnection();
    1019            }
    1020            if (conn == null) {
    1021                throw new SQLException(
    1022                    "Cannot connect using the supplied username/password");
    1023            }
    1024        }
    1025        finally {
    1026            if (conn != null) {
    1027                try {
    1028                    conn.close();
    1029                }
    1030                catch (final SQLException e) {
    1031                    // at least we could connect
    1032                }
    1033            }
    1034        }
    1035        return cpds;
    1036    }
    1037}