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}