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