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