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}