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}