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