    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.dbcp2.datasources;
    019
    020import java.io.IOException;
    021import java.io.ObjectInputStream;
    022import java.sql.Connection;
    023import java.sql.SQLException;
    024
    025import javax.naming.NamingException;
    026import javax.naming.Reference;
    027import javax.naming.StringRefAddr;
    028import javax.sql.ConnectionPoolDataSource;
    029
    030import org.apache.commons.pool2.KeyedObjectPool;
    031import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
    032import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
    033
    034/**
    035 * <p>A pooling <code>DataSource</code> appropriate for deployment within
    036 * J2EE environment.  There are many configuration options, most of which are
    037 * defined in the parent class. All users (based on username) share a single
    038 * maximum number of Connections in this datasource.</p>
    039 *
    040 * <p>User passwords can be changed without re-initializing the datasource.
    041 * When a <code>getConnection(username, password)</code> request is processed
    042 * with a password that is different from those used to create connections in the
    043 * pool associated with <code>username</code>, an attempt is made to create a
    044 * new connection using the supplied password and if this succeeds, idle connections
    045 * created using the old password are destroyed and new connections are created
    046 * using the new password.</p>
    047 *
    048 * @author John D. McNally
    049 * @since 2.0
    050 */
    051public class SharedPoolDataSource extends InstanceKeyDataSource {
    052
    053    private static final long serialVersionUID = -1458539734480586454L;
    054
    055    // Pool properties
    056    private int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
    057
    058
    059    private transient KeyedObjectPool<UserPassKey,PooledConnectionAndInfo> pool = null;
    060    private transient KeyedCPDSConnectionFactory factory = null;
    061
    062    /**
    063     * Default no-arg constructor for Serialization
    064     */
    065    public SharedPoolDataSource() {
    066    }
    067
    068    /**
    069     * Close pool being maintained by this datasource.
    070     */
    071    @Override
    072    public void close() throws Exception {
    073        if (pool != null) {
    074            pool.close();
    075        }
    076        InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
    077    }
    078
    079
    080    // -------------------------------------------------------------------
    081    // Properties
    082
    083    /**
    084     * Set {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
    085     */
    086    public int getMaxTotal() {
    087        return this.maxTotal;
    088    }
    089
    090    /**
    091     * Get {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
    092     */
    093    public void setMaxTotal(final int maxTotal) {
    094        assertInitializationAllowed();
    095        this.maxTotal = maxTotal;
    096    }
    097
    098
    099    // ----------------------------------------------------------------------
    100    // Instrumentation Methods
    101
    102    /**
    103     * Get the number of active connections in the pool.
    104     */
    105    public int getNumActive() {
    106        return pool == null ? 0 : pool.getNumActive();
    107    }
    108
    109    /**
    110     * Get the number of idle connections in the pool.
    111     */
    112    public int getNumIdle() {
    113        return pool == null ? 0 : pool.getNumIdle();
    114    }
    115
    116    // ----------------------------------------------------------------------
    117    // Inherited abstract methods
    118
    119    @Override
    120    protected PooledConnectionAndInfo
    121        getPooledConnectionAndInfo(final String username, final String password)
    122        throws SQLException {
    123
    124        synchronized(this) {
    125            if (pool == null) {
    126                try {
    127                    registerPool(username, password);
    128                } catch (final NamingException e) {
    129                    throw new SQLException("RegisterPool failed", e);
    130                }
    131            }
    132        }
    133
    134        PooledConnectionAndInfo info = null;
    135
    136        final UserPassKey key = new UserPassKey(username, password);
    137
    138        try {
    139            info = pool.borrowObject(key);
    140        }
    141        catch (final Exception e) {
    142            throw new SQLException(
    143                    "Could not retrieve connection info from pool", e);
    144        }
    145        return info;
    146    }
    147
    148    @Override
    149    protected PooledConnectionManager getConnectionManager(final UserPassKey upkey)  {
    150        return factory;
    151    }
    152
    153    /**
    154     * Returns a <code>SharedPoolDataSource</code> {@link Reference}.
    155     */
    156    @Override
    157    public Reference getReference() throws NamingException {
    158        final Reference ref = new Reference(getClass().getName(),
    159            SharedPoolDataSourceFactory.class.getName(), null);
    160        ref.add(new StringRefAddr("instanceKey", getInstanceKey()));
    161        return ref;
    162    }
    163
    164    private void registerPool(final String username, final String password)
    165            throws NamingException, SQLException {
    166
    167        final ConnectionPoolDataSource cpds = testCPDS(username, password);
    168
    169        // Create an object pool to contain our PooledConnections
    170        factory = new KeyedCPDSConnectionFactory(cpds, getValidationQuery(),
    171                getValidationQueryTimeout(), isRollbackAfterValidation());
    172        factory.setMaxConnLifetimeMillis(getMaxConnLifetimeMillis());
    173
    174        final GenericKeyedObjectPoolConfig config =
    175                new GenericKeyedObjectPoolConfig();
    176        config.setBlockWhenExhausted(getDefaultBlockWhenExhausted());
    177        config.setEvictionPolicyClassName(getDefaultEvictionPolicyClassName());
    178        config.setLifo(getDefaultLifo());
    179        config.setMaxIdlePerKey(getDefaultMaxIdle());
    180        config.setMaxTotal(getMaxTotal());
    181        config.setMaxTotalPerKey(getDefaultMaxTotal());
    182        config.setMaxWaitMillis(getDefaultMaxWaitMillis());
    183        config.setMinEvictableIdleTimeMillis(
    184                getDefaultMinEvictableIdleTimeMillis());
    185        config.setMinIdlePerKey(getDefaultMinIdle());
    186        config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun());
    187        config.setSoftMinEvictableIdleTimeMillis(
    188                getDefaultSoftMinEvictableIdleTimeMillis());
    189        config.setTestOnCreate(getDefaultTestOnCreate());
    190        config.setTestOnBorrow(getDefaultTestOnBorrow());
    191        config.setTestOnReturn(getDefaultTestOnReturn());
    192        config.setTestWhileIdle(getDefaultTestWhileIdle());
    193        config.setTimeBetweenEvictionRunsMillis(
    194                getDefaultTimeBetweenEvictionRunsMillis());
    195
    196        final KeyedObjectPool<UserPassKey,PooledConnectionAndInfo> tmpPool =
    197                new GenericKeyedObjectPool<>(factory, config);
    198        factory.setPool(tmpPool);
    199        pool = tmpPool;
    200    }
    201
    202    @Override
    203    protected void setupDefaults(final Connection con, final String username) throws SQLException {
    204        final Boolean defaultAutoCommit = isDefaultAutoCommit();
    205        if (defaultAutoCommit != null &&
    206                con.getAutoCommit() != defaultAutoCommit.booleanValue()) {
    207            con.setAutoCommit(defaultAutoCommit.booleanValue());
    208        }
    209
    210        final int defaultTransactionIsolation = getDefaultTransactionIsolation();
    211        if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
    212            con.setTransactionIsolation(defaultTransactionIsolation);
    213        }
    214
    215        final Boolean defaultReadOnly = isDefaultReadOnly();
    216        if (defaultReadOnly != null &&
    217                con.isReadOnly() != defaultReadOnly.booleanValue()) {
    218            con.setReadOnly(defaultReadOnly.booleanValue());
    219        }
    220    }
    221
    222    /**
    223     * Supports Serialization interface.
    224     *
    225     * @param in a <code>java.io.ObjectInputStream</code> value
    226     * @throws IOException if an error occurs
    227     * @throws ClassNotFoundException if an error occurs
    228     */
    229    private void readObject(final ObjectInputStream in)
    230        throws IOException, ClassNotFoundException {
    231        try
    232        {
    233            in.defaultReadObject();
    234            final SharedPoolDataSource oldDS = (SharedPoolDataSource)
    235                new SharedPoolDataSourceFactory()
    236                    .getObjectInstance(getReference(), null, null, null);
    237            this.pool = oldDS.pool;
    238        }
    239        catch (final NamingException e)
    240        {
    241            throw new IOException("NamingException: " + e);
    242        }
    243    }
    244}
    245