    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 */
    017package org.apache.commons.dbcp2.datasources;
    018
    019import java.io.IOException;
    020import java.io.ObjectInputStream;
    021import java.sql.Connection;
    022import java.sql.SQLException;
    023
    024import javax.naming.NamingException;
    025import javax.naming.Reference;
    026import javax.naming.StringRefAddr;
    027import javax.sql.ConnectionPoolDataSource;
    028
    029import org.apache.commons.pool2.KeyedObjectPool;
    030import org.apache.commons.pool2.KeyedPooledObjectFactory;
    031import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
    032import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
    033
    034/**
    035 * <p>
    036 * A pooling {@code DataSource} appropriate for deployment within J2EE environment. There are many configuration
    037 * options, most of which are defined in the parent class. All users (based on user name) share a single maximum number
    038 * of Connections in this data source.
    039 * </p>
    040 *
    041 * <p>
    042 * User passwords can be changed without re-initializing the data source. When a
    043 * {@code getConnection(user name, password)} request is processed with a password that is different from those
    044 * used to create connections in the pool associated with {@code user name}, an attempt is made to create a new
    045 * connection using the supplied password and if this succeeds, idle connections created using the old password are
    046 * destroyed and new connections are created using the new password.
    047 * </p>
    048 *
    049 * @since 2.0
    050 */
    051public class SharedPoolDataSource extends InstanceKeyDataSource {
    052
    053    private static final long serialVersionUID = -1458539734480586454L;
    054
    055    /**
    056     * Max total defaults to {@link GenericKeyedObjectPoolConfig#DEFAULT_MAX_TOTAL}.
    057     */
    058    private int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
    059
    060    /**
    061     * Maps user credentials to pooled connection with credentials.
    062     */
    063    private transient KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool;
    064
    065    /**
    066     * A {@link KeyedPooledObjectFactory} that creates {@link PoolableConnection}s.
    067     */
    068    private transient KeyedCPDSConnectionFactory factory;
    069
    070    /**
    071     * Default no-argument constructor for Serialization
    072     */
    073    public SharedPoolDataSource() {
    074        // empty.
    075    }
    076
    077    /**
    078     * Closes pool being maintained by this data source.
    079     */
    080    @Override
    081    public void close() throws SQLException {
    082        if (pool != null) {
    083            pool.close();
    084        }
    085        InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
    086    }
    087
    088    @Override
    089    protected PooledConnectionManager getConnectionManager(final UserPassKey userPassKey) {
    090        return factory;
    091    }
    092
    093    /**
    094     * Gets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
    095     *
    096     * @return {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
    097     */
    098    public int getMaxTotal() {
    099        return this.maxTotal;
    100    }
    101
    102    /**
    103     * Gets the number of active connections in the pool.
    104     *
    105     * @return The number of active connections in the pool.
    106     */
    107    public int getNumActive() {
    108        return pool == null ? 0 : pool.getNumActive();
    109    }
    110
    111    /**
    112     * Gets the number of idle connections in the pool.
    113     *
    114     * @return The number of idle connections in the pool.
    115     */
    116    public int getNumIdle() {
    117        return pool == null ? 0 : pool.getNumIdle();
    118    }
    119
    120    @Override
    121    protected PooledConnectionAndInfo getPooledConnectionAndInfo(final String userName, final String userPassword)
    122            throws SQLException {
    123
    124        synchronized (this) {
    125            if (pool == null) {
    126                try {
    127                    registerPool(userName, userPassword);
    128                } catch (final NamingException e) {
    129                    throw new SQLException("registerPool failed", e);
    130                }
    131            }
    132        }
    133
    134        try {
    135            return pool.borrowObject(new UserPassKey(userName, userPassword));
    136        } catch (final Exception e) {
    137            throw new SQLException("Could not retrieve connection info from pool", e);
    138        }
    139    }
    140
    141    /**
    142     * Creates a new {@link Reference} to a {@link SharedPoolDataSource}.
    143     */
    144    @Override
    145    public Reference getReference() throws NamingException {
    146        final Reference ref = new Reference(getClass().getName(), SharedPoolDataSourceFactory.class.getName(), null);
    147        ref.add(new StringRefAddr("instanceKey", getInstanceKey()));
    148        return ref;
    149    }
    150
    151    /**
    152     * Deserializes an instance from an ObjectInputStream.
    153     *
    154     * @param in The source ObjectInputStream.
    155     * @throws IOException            Any of the usual Input/Output related exceptions.
    156     * @throws ClassNotFoundException A class of a serialized object cannot be found.
    157     */
    158    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
    159        in.defaultReadObject();
    160        this.pool = readObjectImpl();
    161    }
    162
    163    private KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> readObjectImpl() throws IOException, ClassNotFoundException {
    164        try {
    165            return ((SharedPoolDataSource) new SharedPoolDataSourceFactory().getObjectInstance(getReference(), null, null, null)).pool;
    166        } catch (final NamingException e) {
    167            throw new IOException("NamingException: " + e);
    168        }
    169    }
    170
    171    private void registerPool(final String userName, final String password) throws NamingException, SQLException {
    172
    173        final ConnectionPoolDataSource cpds = testCPDS(userName, password);
    174
    175        // Create an object pool to contain our PooledConnections
    176        factory = new KeyedCPDSConnectionFactory(cpds, getValidationQuery(), getValidationQueryTimeoutDuration(), isRollbackAfterValidation());
    177        factory.setMaxConn(getMaxConnDuration());
    178
    179        final GenericKeyedObjectPoolConfig<PooledConnectionAndInfo> config = new GenericKeyedObjectPoolConfig<>();
    180        config.setBlockWhenExhausted(getDefaultBlockWhenExhausted());
    181        config.setEvictionPolicyClassName(getDefaultEvictionPolicyClassName());
    182        config.setLifo(getDefaultLifo());
    183        config.setMaxIdlePerKey(getDefaultMaxIdle());
    184        config.setMaxTotal(getMaxTotal());
    185        config.setMaxTotalPerKey(getDefaultMaxTotal());
    186        config.setMaxWait(getDefaultMaxWait());
    187        config.setMinEvictableIdleDuration(getDefaultMinEvictableIdleDuration());
    188        config.setMinIdlePerKey(getDefaultMinIdle());
    189        config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun());
    190        config.setSoftMinEvictableIdleDuration(getDefaultSoftMinEvictableIdleDuration());
    191        config.setTestOnCreate(getDefaultTestOnCreate());
    192        config.setTestOnBorrow(getDefaultTestOnBorrow());
    193        config.setTestOnReturn(getDefaultTestOnReturn());
    194        config.setTestWhileIdle(getDefaultTestWhileIdle());
    195        config.setTimeBetweenEvictionRuns(getDefaultDurationBetweenEvictionRuns());
    196
    197        final KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> tmpPool = new GenericKeyedObjectPool<>(factory, config);
    198        factory.setPool(tmpPool);
    199        pool = tmpPool;
    200    }
    201
    202    /**
    203     * Sets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
    204     *
    205     * @param maxTotal
    206     *            {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
    207     */
    208    public void setMaxTotal(final int maxTotal) {
    209        assertInitializationAllowed();
    210        this.maxTotal = maxTotal;
    211    }
    212
    213    @Override
    214    protected void setupDefaults(final Connection connection, final String userName) throws SQLException {
    215        final Boolean defaultAutoCommit = isDefaultAutoCommit();
    216        if (defaultAutoCommit != null && connection.getAutoCommit() != defaultAutoCommit) {
    217            connection.setAutoCommit(defaultAutoCommit);
    218        }
    219
    220        final int defaultTransactionIsolation = getDefaultTransactionIsolation();
    221        if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
    222            connection.setTransactionIsolation(defaultTransactionIsolation);
    223        }
    224
    225        final Boolean defaultReadOnly = isDefaultReadOnly();
    226        if (defaultReadOnly != null && connection.isReadOnly() != defaultReadOnly) {
    227            connection.setReadOnly(defaultReadOnly);
    228        }
    229    }
    230
    231    @Override
    232    protected void toStringFields(final StringBuilder builder) {
    233        super.toStringFields(builder);
    234        builder.append(", maxTotal=");
    235        builder.append(maxTotal);
    236    }
    237}