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 {@link 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.setMinEvictableIdleDuration(getDefaultMinEvictableIdleDuration());
181        config.setMinIdlePerKey(getDefaultMinIdle());
182        config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun());
183        config.setSoftMinEvictableIdleDuration(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}