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