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