    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>
    036 * A pooling <code>DataSource</code> 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)</code> request is processed with a password that is different from those
    044 * used to create connections in the pool associated with <code>user name</code>, 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    // Pool properties
    056    private int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
    057
    058    private transient KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool;
    059    private transient KeyedCPDSConnectionFactory factory;
    060
    061    /**
    062     * Default no-argument constructor for Serialization
    063     */
    064    public SharedPoolDataSource() {
    065        // empty.
    066    }
    067
    068    /**
    069     * Closes pool being maintained by this data source.
    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    // Properties
    081
    082    /**
    083     * Gets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
    084     *
    085     * @return {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
    086     */
    087    public int getMaxTotal() {
    088        return this.maxTotal;
    089    }
    090
    091    /**
    092     * Sets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
    093     *
    094     * @param maxTotal
    095     *            {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
    096     */
    097    public void setMaxTotal(final int maxTotal) {
    098        assertInitializationAllowed();
    099        this.maxTotal = maxTotal;
    100    }
    101
    102    // ----------------------------------------------------------------------
    103    // Instrumentation Methods
    104
    105    /**
    106     * Gets the number of active connections in the pool.
    107     *
    108     * @return The number of active connections in the pool.
    109     */
    110    public int getNumActive() {
    111        return pool == null ? 0 : pool.getNumActive();
    112    }
    113
    114    /**
    115     * Gets the number of idle connections in the pool.
    116     *
    117     * @return The number of idle connections in the pool.
    118     */
    119    public int getNumIdle() {
    120        return pool == null ? 0 : pool.getNumIdle();
    121    }
    122
    123    // ----------------------------------------------------------------------
    124    // Inherited abstract methods
    125
    126    @Override
    127    protected PooledConnectionAndInfo getPooledConnectionAndInfo(final String userName, final String userPassword)
    128            throws SQLException {
    129
    130        synchronized (this) {
    131            if (pool == null) {
    132                try {
    133                    registerPool(userName, userPassword);
    134                } catch (final NamingException e) {
    135                    throw new SQLException("RegisterPool failed", e);
    136                }
    137            }
    138        }
    139
    140        PooledConnectionAndInfo info = null;
    141
    142        final UserPassKey key = new UserPassKey(userName, userPassword);
    143
    144        try {
    145            info = pool.borrowObject(key);
    146        } catch (final Exception e) {
    147            throw new SQLException("Could not retrieve connection info from pool", e);
    148        }
    149        return info;
    150    }
    151
    152    @Override
    153    protected PooledConnectionManager getConnectionManager(final UserPassKey upkey) {
    154        return factory;
    155    }
    156
    157    /**
    158     * Returns a <code>SharedPoolDataSource</code> {@link Reference}.
    159     */
    160    @Override
    161    public Reference getReference() throws NamingException {
    162        final Reference ref = new Reference(getClass().getName(), SharedPoolDataSourceFactory.class.getName(), null);
    163        ref.add(new StringRefAddr("instanceKey", getInstanceKey()));
    164        return ref;
    165    }
    166
    167    private void registerPool(final String userName, final String password) throws NamingException, SQLException {
    168
    169        final ConnectionPoolDataSource cpds = testCPDS(userName, password);
    170
    171        // Create an object pool to contain our PooledConnections
    172        factory = new KeyedCPDSConnectionFactory(cpds, getValidationQuery(), getValidationQueryTimeout(),
    173                isRollbackAfterValidation());
    174        factory.setMaxConnLifetimeMillis(getMaxConnLifetimeMillis());
    175
    176        final GenericKeyedObjectPoolConfig<PooledConnectionAndInfo> config = new GenericKeyedObjectPoolConfig<>();
    177        config.setBlockWhenExhausted(getDefaultBlockWhenExhausted());
    178        config.setEvictionPolicyClassName(getDefaultEvictionPolicyClassName());
    179        config.setLifo(getDefaultLifo());
    180        config.setMaxIdlePerKey(getDefaultMaxIdle());
    181        config.setMaxTotal(getMaxTotal());
    182        config.setMaxTotalPerKey(getDefaultMaxTotal());
    183        config.setMaxWaitMillis(getDefaultMaxWaitMillis());
    184        config.setMinEvictableIdleTimeMillis(getDefaultMinEvictableIdleTimeMillis());
    185        config.setMinIdlePerKey(getDefaultMinIdle());
    186        config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun());
    187        config.setSoftMinEvictableIdleTimeMillis(getDefaultSoftMinEvictableIdleTimeMillis());
    188        config.setTestOnCreate(getDefaultTestOnCreate());
    189        config.setTestOnBorrow(getDefaultTestOnBorrow());
    190        config.setTestOnReturn(getDefaultTestOnReturn());
    191        config.setTestWhileIdle(getDefaultTestWhileIdle());
    192        config.setTimeBetweenEvictionRunsMillis(getDefaultTimeBetweenEvictionRunsMillis());
    193
    194        final KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> tmpPool = new GenericKeyedObjectPool<>(factory,
    195                config);
    196        factory.setPool(tmpPool);
    197        pool = tmpPool;
    198    }
    199
    200    @Override
    201    protected void setupDefaults(final Connection connection, final String userName) throws SQLException {
    202        final Boolean defaultAutoCommit = isDefaultAutoCommit();
    203        if (defaultAutoCommit != null && connection.getAutoCommit() != defaultAutoCommit.booleanValue()) {
    204            connection.setAutoCommit(defaultAutoCommit.booleanValue());
    205        }
    206
    207        final int defaultTransactionIsolation = getDefaultTransactionIsolation();
    208        if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
    209            connection.setTransactionIsolation(defaultTransactionIsolation);
    210        }
    211
    212        final Boolean defaultReadOnly = isDefaultReadOnly();
    213        if (defaultReadOnly != null && connection.isReadOnly() != defaultReadOnly.booleanValue()) {
    214            connection.setReadOnly(defaultReadOnly.booleanValue());
    215        }
    216    }
    217
    218    /**
    219     * Supports Serialization interface.
    220     *
    221     * @param in
    222     *            a <code>java.io.ObjectInputStream</code> value
    223     * @throws IOException
    224     *             if an error occurs
    225     * @throws ClassNotFoundException
    226     *             if an error occurs
    227     */
    228    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
    229        try {
    230            in.defaultReadObject();
    231            final SharedPoolDataSource oldDS = (SharedPoolDataSource) new SharedPoolDataSourceFactory()
    232                    .getObjectInstance(getReference(), null, null, null);
    233            this.pool = oldDS.pool;
    234        } catch (final NamingException e) {
    235            throw new IOException("NamingException: " + e);
    236        }
    237    }
    238
    239    @Override
    240    protected void toStringFields(final StringBuilder builder) {
    241        super.toStringFields(builder);
    242        builder.append(", maxTotal=");
    243        builder.append(maxTotal);
    244    }
    245}