    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.cpdsadapter;
    019
    020import java.io.PrintWriter;
    021import java.io.Serializable;
    022import java.sql.DriverManager;
    023import java.sql.SQLException;
    024import java.sql.SQLFeatureNotSupportedException;
    025import java.util.Hashtable;
    026import java.util.Properties;
    027import java.util.logging.Logger;
    028
    029import javax.naming.Context;
    030import javax.naming.Name;
    031import javax.naming.NamingException;
    032import javax.naming.RefAddr;
    033import javax.naming.Reference;
    034import javax.naming.Referenceable;
    035import javax.naming.StringRefAddr;
    036import javax.naming.spi.ObjectFactory;
    037import javax.sql.ConnectionPoolDataSource;
    038import javax.sql.PooledConnection;
    039
    040import org.apache.commons.dbcp2.PoolablePreparedStatement;
    041import org.apache.commons.pool2.KeyedObjectPool;
    042import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
    043import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
    044import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
    045
    046/**
    047 * <p>
    048 * An adapter for JDBC drivers that do not include an implementation
    049 * of {@link javax.sql.ConnectionPoolDataSource}, but still include a
    050 * {@link java.sql.DriverManager} implementation.
    051 * <code>ConnectionPoolDataSource</code>s are not used within general
    052 * applications.  They are used by <code>DataSource</code> implementations
    053 * that pool <code>Connection</code>s, such as
    054 * {@link org.apache.commons.dbcp2.datasources.SharedPoolDataSource}.  A J2EE
    055 * container will normally provide some method of initializing the
    056 * <code>ConnectionPoolDataSource</code> whose attributes are presented
    057 * as bean getters/setters and then deploying it via JNDI.  It is then
    058 * available as a source of physical connections to the database, when
    059 * the pooling <code>DataSource</code> needs to create a new
    060 * physical connection.
    061 * </p>
    062 *
    063 * <p>
    064 * Although normally used within a JNDI environment, the DriverAdapterCPDS
    065 * can be instantiated and initialized as any bean and then attached
    066 * directly to a pooling <code>DataSource</code>.
    067 * <code>Jdbc2PoolDataSource</code> can use the
    068 * <code>ConnectionPoolDataSource</code> with or without the use of JNDI.
    069 * </p>
    070 *
    071 * <p>
    072 * The DriverAdapterCPDS also provides <code>PreparedStatement</code> pooling
    073 * which is not generally available in jdbc2
    074 * <code>ConnectionPoolDataSource</code> implementation, but is
    075 * addressed within the jdbc3 specification.  The <code>PreparedStatement</code>
    076 * pool in DriverAdapterCPDS has been in the dbcp package for some time, but
    077 * it has not undergone extensive testing in the configuration used here.
    078 * It should be considered experimental and can be toggled with the
    079 * poolPreparedStatements attribute.
    080 * </p>
    081 *
    082 * <p>
    083 * The <a href="package-summary.html">package documentation</a> contains an
    084 * example using catalina and JNDI.  The <a
    085 * href="../datasources/package-summary.html">datasources package documentation</a>
    086 * shows how to use <code>DriverAdapterCPDS</code> as a source for
    087 * <code>Jdbc2PoolDataSource</code> without the use of JNDI.
    088 * </p>
    089 *
    090 * @author John D. McNally
    091 * @since 2.0
    092 */
    093public class DriverAdapterCPDS
    094    implements ConnectionPoolDataSource, Referenceable, Serializable,
    095               ObjectFactory {
    096
    097    private static final String KEY_USER = "user";
    098
    099
    100    private static final String KEY_PASSWORD = "password";
    101
    102
    103    private static final long serialVersionUID = -4820523787212147844L;
    104
    105
    106    private static final String GET_CONNECTION_CALLED
    107            = "A PooledConnection was already requested from this source, "
    108            + "further initialization is not allowed.";
    109
    110    /** Description */
    111    private String description;
    112    /** Password */
    113    private String password;
    114    /** Url name */
    115    private String url;
    116    /** User name */
    117    private String user;
    118    /** Driver class name */
    119    private String driver;
    120
    121    /** Login TimeOut in seconds */
    122    private int loginTimeout;
    123    /** Log stream. NOT USED */
    124    private transient PrintWriter logWriter = null;
    125
    126    // PreparedStatement pool properties
    127    private boolean poolPreparedStatements;
    128    private int maxIdle = 10;
    129    private long _timeBetweenEvictionRunsMillis =
    130            BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
    131    private int _numTestsPerEvictionRun = -1;
    132    private int _minEvictableIdleTimeMillis = -1;
    133    private int _maxPreparedStatements = -1;
    134
    135    /** Whether or not getConnection has been called */
    136    private volatile boolean getConnectionCalled = false;
    137
    138    /** Connection properties passed to JDBC Driver */
    139    private Properties connectionProperties = null;
    140
    141    static {
    142        // Attempt to prevent deadlocks - see DBCP - 272
    143        DriverManager.getDrivers();
    144    }
    145
    146    /**
    147     * Controls access to the underlying connection
    148     */
    149    private boolean accessToUnderlyingConnectionAllowed = false;
    150
    151    /**
    152     * Default no-arg constructor for Serialization
    153     */
    154    public DriverAdapterCPDS() {
    155    }
    156
    157    /**
    158     * Attempt to establish a database connection using the default
    159     * user and password.
    160     */
    161    @Override
    162    public PooledConnection getPooledConnection() throws SQLException {
    163        return getPooledConnection(getUser(), getPassword());
    164    }
    165
    166    /**
    167     * Attempt to establish a database connection.
    168     * @param username name to be used for the connection
    169     * @param pass password to be used fur the connection
    170     */
    171    @Override
    172    public PooledConnection getPooledConnection(final String username, final String pass)
    173            throws SQLException {
    174        getConnectionCalled = true;
    175        PooledConnectionImpl pci = null;
    176        // Workaround for buggy WebLogic 5.1 classloader - ignore the
    177        // exception upon first invocation.
    178        try {
    179            if (connectionProperties != null) {
    180                update(connectionProperties, KEY_USER, username);
    181                update(connectionProperties, KEY_PASSWORD, pass);
    182                pci = new PooledConnectionImpl(DriverManager.getConnection(
    183                        getUrl(), connectionProperties));
    184            } else {
    185                pci = new PooledConnectionImpl(DriverManager.getConnection(
    186                        getUrl(), username, pass));
    187            }
    188            pci.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
    189        }
    190        catch (final ClassCircularityError e)
    191        {
    192            if (connectionProperties != null) {
    193                pci = new PooledConnectionImpl(DriverManager.getConnection(
    194                        getUrl(), connectionProperties));
    195            } else {
    196                pci = new PooledConnectionImpl(DriverManager.getConnection(
    197                        getUrl(), username, pass));
    198            }
    199            pci.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
    200        }
    201        KeyedObjectPool<PStmtKeyCPDS, PoolablePreparedStatement<PStmtKeyCPDS>> stmtPool = null;
    202        if (isPoolPreparedStatements()) {
    203            final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
    204            config.setMaxTotalPerKey(Integer.MAX_VALUE);
    205            config.setBlockWhenExhausted(false);
    206            config.setMaxWaitMillis(0);
    207            config.setMaxIdlePerKey(getMaxIdle());
    208            if (getMaxPreparedStatements() <= 0)
    209            {
    210                // since there is no limit, create a prepared statement pool with an eviction thread
    211                //  evictor settings are the same as the connection pool settings.
    212                config.setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis());
    213                config.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
    214                config.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis());
    215            }
    216            else
    217            {
    218                // since there is limit, create a prepared statement pool without an eviction thread
    219                //  pool has LRU functionality so when the limit is reached, 15% of the pool is cleared.
    220                // see org.apache.commons.pool2.impl.GenericKeyedObjectPool.clearOldest method
    221                config.setMaxTotal(getMaxPreparedStatements());
    222                config.setTimeBetweenEvictionRunsMillis(-1);
    223                config.setNumTestsPerEvictionRun(0);
    224                config.setMinEvictableIdleTimeMillis(0);
    225            }
    226            stmtPool = new GenericKeyedObjectPool<>(pci, config);
    227            pci.setStatementPool(stmtPool);
    228        }
    229        return pci;
    230    }
    231
    232    @Override
    233    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    234        throw new SQLFeatureNotSupportedException();
    235    }
    236
    237    // ----------------------------------------------------------------------
    238    // Referenceable implementation
    239
    240    /**
    241     * <CODE>Referenceable</CODE> implementation.
    242     */
    243    @Override
    244    public Reference getReference() throws NamingException {
    245        // this class implements its own factory
    246        final String factory = getClass().getName();
    247
    248        final Reference ref = new Reference(getClass().getName(), factory, null);
    249
    250        ref.add(new StringRefAddr("description", getDescription()));
    251        ref.add(new StringRefAddr("driver", getDriver()));
    252        ref.add(new StringRefAddr("loginTimeout",
    253                                  String.valueOf(getLoginTimeout())));
    254        ref.add(new StringRefAddr(KEY_PASSWORD, getPassword()));
    255        ref.add(new StringRefAddr(KEY_USER, getUser()));
    256        ref.add(new StringRefAddr("url", getUrl()));
    257
    258        ref.add(new StringRefAddr("poolPreparedStatements",
    259                                  String.valueOf(isPoolPreparedStatements())));
    260        ref.add(new StringRefAddr("maxIdle",
    261                                  String.valueOf(getMaxIdle())));
    262        ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis",
    263            String.valueOf(getTimeBetweenEvictionRunsMillis())));
    264        ref.add(new StringRefAddr("numTestsPerEvictionRun",
    265            String.valueOf(getNumTestsPerEvictionRun())));
    266        ref.add(new StringRefAddr("minEvictableIdleTimeMillis",
    267            String.valueOf(getMinEvictableIdleTimeMillis())));
    268        ref.add(new StringRefAddr("maxPreparedStatements",
    269            String.valueOf(getMaxPreparedStatements())));
    270
    271        return ref;
    272    }
    273
    274
    275    // ----------------------------------------------------------------------
    276    // ObjectFactory implementation
    277
    278    /**
    279     * implements ObjectFactory to create an instance of this class
    280     */
    281    @Override
    282    public Object getObjectInstance(final Object refObj, final Name name,
    283                                    final Context context, final Hashtable<?,?> env)
    284            throws Exception {
    285        // The spec says to return null if we can't create an instance
    286        // of the reference
    287        DriverAdapterCPDS cpds = null;
    288        if (refObj instanceof Reference) {
    289            final Reference ref = (Reference)refObj;
    290            if (ref.getClassName().equals(getClass().getName())) {
    291                RefAddr ra = ref.get("description");
    292                if (ra != null && ra.getContent() != null) {
    293                    setDescription(ra.getContent().toString());
    294                }
    295
    296                ra = ref.get("driver");
    297                if (ra != null && ra.getContent() != null) {
    298                    setDriver(ra.getContent().toString());
    299                }
    300                ra = ref.get("url");
    301                if (ra != null && ra.getContent() != null) {
    302                    setUrl(ra.getContent().toString());
    303                }
    304                ra = ref.get(KEY_USER);
    305                if (ra != null && ra.getContent() != null) {
    306                    setUser(ra.getContent().toString());
    307                }
    308                ra = ref.get(KEY_PASSWORD);
    309                if (ra != null && ra.getContent() != null) {
    310                    setPassword(ra.getContent().toString());
    311                }
    312
    313                ra = ref.get("poolPreparedStatements");
    314                if (ra != null && ra.getContent() != null) {
    315                    setPoolPreparedStatements(Boolean.valueOf(
    316                        ra.getContent().toString()).booleanValue());
    317                }
    318                ra = ref.get("maxIdle");
    319                if (ra != null && ra.getContent() != null) {
    320                    setMaxIdle(Integer.parseInt(ra.getContent().toString()));
    321                }
    322
    323                ra = ref.get("timeBetweenEvictionRunsMillis");
    324                if (ra != null && ra.getContent() != null) {
    325                    setTimeBetweenEvictionRunsMillis(
    326                        Integer.parseInt(ra.getContent().toString()));
    327                }
    328
    329                ra = ref.get("numTestsPerEvictionRun");
    330                if (ra != null && ra.getContent() != null) {
    331                    setNumTestsPerEvictionRun(
    332                        Integer.parseInt(ra.getContent().toString()));
    333                }
    334
    335                ra = ref.get("minEvictableIdleTimeMillis");
    336                if (ra != null && ra.getContent() != null) {
    337                    setMinEvictableIdleTimeMillis(
    338                        Integer.parseInt(ra.getContent().toString()));
    339                }
    340                ra = ref.get("maxPreparedStatements");
    341                if (ra != null && ra.getContent() != null) {
    342                    setMaxPreparedStatements(
    343                        Integer.parseInt(ra.getContent().toString()));
    344                }
    345
    346                ra = ref.get("accessToUnderlyingConnectionAllowed");
    347                if (ra != null && ra.getContent() != null) {
    348                    setAccessToUnderlyingConnectionAllowed(
    349                            Boolean.valueOf(ra.getContent().toString()).booleanValue());
    350                }
    351
    352                cpds = this;
    353            }
    354        }
    355        return cpds;
    356    }
    357
    358    /**
    359     * Throws an IllegalStateException, if a PooledConnection has already
    360     * been requested.
    361     */
    362    private void assertInitializationAllowed() throws IllegalStateException {
    363        if (getConnectionCalled) {
    364            throw new IllegalStateException(GET_CONNECTION_CALLED);
    365        }
    366    }
    367
    368    // ----------------------------------------------------------------------
    369    // Properties
    370
    371    /**
    372     * Gets the connection properties passed to the JDBC driver.
    373     *
    374     * @return the JDBC connection properties used when creating connections.
    375     */
    376    public Properties getConnectionProperties() {
    377        return connectionProperties;
    378    }
    379
    380    /**
    381     * <p>Sets the connection properties passed to the JDBC driver.</p>
    382     *
    383     * <p>If <code>props</code> contains "user" and/or "password"
    384     * properties, the corresponding instance properties are set. If these
    385     * properties are not present, they are filled in using
    386     * {@link #getUser()}, {@link #getPassword()} when {@link #getPooledConnection()}
    387     * is called, or using the actual parameters to the method call when
    388     * {@link #getPooledConnection(String, String)} is called. Calls to
    389     * {@link #setUser(String)} or {@link #setPassword(String)} overwrite the values
    390     * of these properties if <code>connectionProperties</code> is not null.</p>
    391     *
    392     * @param props Connection properties to use when creating new connections.
    393     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
    394     */
    395    public void setConnectionProperties(final Properties props) {
    396        assertInitializationAllowed();
    397        connectionProperties = props;
    398        if (connectionProperties != null) {
    399            if (connectionProperties.containsKey(KEY_USER)) {
    400                setUser(connectionProperties.getProperty(KEY_USER));
    401            }
    402            if (connectionProperties.containsKey(KEY_PASSWORD)) {
    403                setPassword(connectionProperties.getProperty(KEY_PASSWORD));
    404            }
    405        }
    406    }
    407
    408    /**
    409     * Gets the value of description.  This property is here for use by
    410     * the code which will deploy this datasource.  It is not used
    411     * internally.
    412     *
    413     * @return value of description, may be null.
    414     * @see #setDescription(String)
    415     */
    416    public String getDescription() {
    417        return description;
    418    }
    419
    420    /**
    421     * Sets the value of description.  This property is here for use by
    422     * the code which will deploy this datasource.  It is not used
    423     * internally.
    424     *
    425     * @param v  Value to assign to description.
    426     */
    427    public void setDescription(final String  v) {
    428        this.description = v;
    429    }
    430
    431    /**
    432     * Gets the value of password for the default user.
    433     * @return value of password.
    434     */
    435    public String getPassword() {
    436        return password;
    437    }
    438
    439    /**
    440     * Sets the value of password for the default user.
    441     * @param v  Value to assign to password.
    442     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
    443     */
    444    public void setPassword(final String v) {
    445        assertInitializationAllowed();
    446        this.password = v;
    447        update(connectionProperties, KEY_PASSWORD, v);
    448    }
    449
    450    /**
    451     * Gets the value of url used to locate the database for this datasource.
    452     * @return value of url.
    453     */
    454    public String getUrl() {
    455        return url;
    456    }
    457
    458    /**
    459     * Sets the value of URL string used to locate the database for this datasource.
    460     * @param v  Value to assign to url.
    461     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
    462    */
    463    public void setUrl(final String v) {
    464        assertInitializationAllowed();
    465        this.url = v;
    466    }
    467
    468    /**
    469     * Gets the value of default user (login or username).
    470     * @return value of user.
    471     */
    472    public String getUser() {
    473        return user;
    474    }
    475
    476    /**
    477     * Sets the value of default user (login or username).
    478     * @param v  Value to assign to user.
    479     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
    480     */
    481    public void setUser(final String v) {
    482        assertInitializationAllowed();
    483        this.user = v;
    484        update(connectionProperties, KEY_USER, v);
    485    }
    486
    487    /**
    488     * Gets the driver classname.
    489     * @return value of driver.
    490     */
    491    public String getDriver() {
    492        return driver;
    493    }
    494
    495    /**
    496     * Sets the driver classname.  Setting the driver classname cause the
    497     * driver to be registered with the DriverManager.
    498     * @param v  Value to assign to driver.
    499     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
    500     * @throws ClassNotFoundException if the class cannot be located
    501     */
    502    public void setDriver(final String v) throws ClassNotFoundException {
    503        assertInitializationAllowed();
    504        this.driver = v;
    505        // make sure driver is registered
    506        Class.forName(v);
    507    }
    508
    509    /**
    510     * Gets the maximum time in seconds that this data source can wait
    511     * while attempting to connect to a database. NOT USED.
    512     */
    513    @Override
    514    public int getLoginTimeout() {
    515        return loginTimeout;
    516    }
    517
    518    /**
    519     * Gets the log writer for this data source. NOT USED.
    520     */
    521    @Override
    522    public PrintWriter getLogWriter() {
    523        return logWriter;
    524    }
    525
    526    /**
    527     * Sets the maximum time in seconds that this data source will wait
    528     * while attempting to connect to a database. NOT USED.
    529     */
    530    @Override
    531    public void setLoginTimeout(final int seconds) {
    532        loginTimeout = seconds;
    533    }
    534
    535    /**
    536     * Sets the log writer for this data source. NOT USED.
    537     */
    538    @Override
    539    public void setLogWriter(final PrintWriter out) {
    540        logWriter = out;
    541    }
    542
    543
    544    // ------------------------------------------------------------------
    545    // PreparedStatement pool properties
    546
    547
    548    /**
    549     * Flag to toggle the pooling of <code>PreparedStatement</code>s
    550     * @return value of poolPreparedStatements.
    551     */
    552    public boolean isPoolPreparedStatements() {
    553        return poolPreparedStatements;
    554    }
    555
    556    /**
    557     * Flag to toggle the pooling of <code>PreparedStatement</code>s
    558     * @param v  true to pool statements.
    559     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
    560     */
    561    public void setPoolPreparedStatements(final boolean v) {
    562        assertInitializationAllowed();
    563        this.poolPreparedStatements = v;
    564    }
    565
    566    /**
    567     * Gets the maximum number of statements that can remain idle in the
    568     * pool, without extra ones being released, or negative for no limit.
    569     * @return the value of maxIdle
    570     */
    571    public int getMaxIdle() {
    572        return this.maxIdle;
    573    }
    574
    575    /**
    576     * Gets the maximum number of statements that can remain idle in the
    577     * pool, without extra ones being released, or negative for no limit.
    578     *
    579     * @param maxIdle The maximum number of statements that can remain idle
    580     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
    581     */
    582    public void setMaxIdle(final int maxIdle) {
    583        assertInitializationAllowed();
    584        this.maxIdle = maxIdle;
    585    }
    586
    587    /**
    588     * Gets the number of milliseconds to sleep between runs of the
    589     * idle object evictor thread.
    590     * When non-positive, no idle object evictor thread will be
    591     * run.
    592     * @return the value of the evictor thread timer
    593     * @see #setTimeBetweenEvictionRunsMillis(long)
    594     */
    595    public long getTimeBetweenEvictionRunsMillis() {
    596        return _timeBetweenEvictionRunsMillis;
    597    }
    598
    599    /**
    600     * Sets the number of milliseconds to sleep between runs of the
    601     * idle object evictor thread.
    602     * When non-positive, no idle object evictor thread will be
    603     * run.
    604     * @param timeBetweenEvictionRunsMillis
    605     * @see #getTimeBetweenEvictionRunsMillis()
    606     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
    607     */
    608    public void setTimeBetweenEvictionRunsMillis(
    609            final long timeBetweenEvictionRunsMillis) {
    610        assertInitializationAllowed();
    611        _timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    612    }
    613
    614    /**
    615     * Gets the number of statements to examine during each run of the
    616     * idle object evictor thread (if any.)
    617     *
    618     * *see #setNumTestsPerEvictionRun
    619     * *see #setTimeBetweenEvictionRunsMillis
    620     * @return the number of statements to examine during each run of the idle object evictor thread (if any.)
    621     */
    622    public int getNumTestsPerEvictionRun() {
    623        return _numTestsPerEvictionRun;
    624    }
    625
    626    /**
    627     * Sets the number of statements to examine during each run of the
    628     * idle object evictor thread (if any).
    629     * <p>
    630     * When a negative value is supplied, <tt>ceil({*link #numIdle})/abs({*link #getNumTestsPerEvictionRun})</tt>
    631     * tests will be run.  I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the
    632     * idle objects will be tested per run.
    633     *
    634     * @param numTestsPerEvictionRun number of statements to examine per run
    635     * @see #getNumTestsPerEvictionRun()
    636     * @see #setTimeBetweenEvictionRunsMillis(long)
    637     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
    638     */
    639    public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
    640        assertInitializationAllowed();
    641        _numTestsPerEvictionRun = numTestsPerEvictionRun;
    642    }
    643
    644    /**
    645     * Gets the minimum amount of time a statement may sit idle in the pool
    646     * before it is eligible for eviction by the idle object evictor
    647     * (if any).
    648     *
    649     * *see #setMinEvictableIdleTimeMillis
    650     * *see #setTimeBetweenEvictionRunsMillis
    651     * @return the minimum amount of time a statement may sit idle in the pool.
    652     */
    653    public int getMinEvictableIdleTimeMillis() {
    654        return _minEvictableIdleTimeMillis;
    655    }
    656
    657    /**
    658     * Sets the minimum amount of time a statement may sit idle in the pool
    659     * before it is eligible for eviction by the idle object evictor
    660     * (if any).
    661     * When non-positive, no objects will be evicted from the pool
    662     * due to idle time alone.
    663     * @param minEvictableIdleTimeMillis minimum time to set (in ms)
    664     * @see #getMinEvictableIdleTimeMillis()
    665     * @see #setTimeBetweenEvictionRunsMillis(long)
    666     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
    667     */
    668    public void setMinEvictableIdleTimeMillis(final int minEvictableIdleTimeMillis) {
    669        assertInitializationAllowed();
    670        _minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    671    }
    672
    673    /**
    674     * Returns the value of the accessToUnderlyingConnectionAllowed property.
    675     *
    676     * @return true if access to the underlying is allowed, false otherwise.
    677     */
    678    public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
    679        return this.accessToUnderlyingConnectionAllowed;
    680    }
    681
    682    /**
    683     * Sets the value of the accessToUnderlyingConnectionAllowed property.
    684     * It controls if the PoolGuard allows access to the underlying connection.
    685     * (Default: false)
    686     *
    687     * @param allow Access to the underlying connection is granted when true.
    688     */
    689    public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
    690        this.accessToUnderlyingConnectionAllowed = allow;
    691    }
    692
    693    /**
    694     * Gets the maximum number of prepared statements.
    695     *
    696     * @return maxPrepartedStatements value
    697     */
    698    public int getMaxPreparedStatements()
    699    {
    700        return _maxPreparedStatements;
    701    }
    702
    703    /**
    704     * Sets the maximum number of prepared statements.
    705     * @param maxPreparedStatements the new maximum number of prepared
    706     * statements
    707     */
    708    public void setMaxPreparedStatements(final int maxPreparedStatements)
    709    {
    710        _maxPreparedStatements = maxPreparedStatements;
    711    }
    712
    713    private void update(final Properties properties, final String key, final String value) {
    714        if (properties != null) {
    715            if (value == null) {
    716                properties.remove(key);
    717            } else {
    718                properties.setProperty(key, value);
    719            }
    720        }
    721    }
    722}