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 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}