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>A pooling <code>DataSource</code> appropriate for deployment within
036 * J2EE environment.  There are many configuration options, most of which are
037 * defined in the parent class. All users (based on username) share a single
038 * maximum number of Connections in this datasource.</p>
039 *
040 * <p>User passwords can be changed without re-initializing the datasource.
041 * When a <code>getConnection(username, password)</code> request is processed
042 * with a password that is different from those used to create connections in the
043 * pool associated with <code>username</code>, an attempt is made to create a
044 * new connection using the supplied password and if this succeeds, idle connections
045 * created using the old password are destroyed and new connections are created
046 * using the new password.</p>
047 *
048 * @author John D. McNally
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
059    private transient KeyedObjectPool<UserPassKey,PooledConnectionAndInfo> pool = null;
060    private transient KeyedCPDSConnectionFactory factory = null;
061
062    /**
063     * Default no-arg constructor for Serialization
064     */
065    public SharedPoolDataSource() {
066    }
067
068    /**
069     * Close pool being maintained by this datasource.
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    // -------------------------------------------------------------------
081    // Properties
082
083    /**
084     * Set {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
085     */
086    public int getMaxTotal() {
087        return this.maxTotal;
088    }
089
090    /**
091     * Get {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
092     */
093    public void setMaxTotal(final int maxTotal) {
094        assertInitializationAllowed();
095        this.maxTotal = maxTotal;
096    }
097
098
099    // ----------------------------------------------------------------------
100    // Instrumentation Methods
101
102    /**
103     * Get the number of active connections in the pool.
104     */
105    public int getNumActive() {
106        return pool == null ? 0 : pool.getNumActive();
107    }
108
109    /**
110     * Get the number of idle connections in the pool.
111     */
112    public int getNumIdle() {
113        return pool == null ? 0 : pool.getNumIdle();
114    }
115
116    // ----------------------------------------------------------------------
117    // Inherited abstract methods
118
119    @Override
120    protected PooledConnectionAndInfo
121        getPooledConnectionAndInfo(final String username, final String password)
122        throws SQLException {
123
124        synchronized(this) {
125            if (pool == null) {
126                try {
127                    registerPool(username, password);
128                } catch (final NamingException e) {
129                    throw new SQLException("RegisterPool failed", e);
130                }
131            }
132        }
133
134        PooledConnectionAndInfo info = null;
135
136        final UserPassKey key = new UserPassKey(username, password);
137
138        try {
139            info = pool.borrowObject(key);
140        }
141        catch (final Exception e) {
142            throw new SQLException(
143                    "Could not retrieve connection info from pool", e);
144        }
145        return info;
146    }
147
148    @Override
149    protected PooledConnectionManager getConnectionManager(final UserPassKey upkey)  {
150        return factory;
151    }
152
153    /**
154     * Returns a <code>SharedPoolDataSource</code> {@link Reference}.
155     */
156    @Override
157    public Reference getReference() throws NamingException {
158        final Reference ref = new Reference(getClass().getName(),
159            SharedPoolDataSourceFactory.class.getName(), null);
160        ref.add(new StringRefAddr("instanceKey", getInstanceKey()));
161        return ref;
162    }
163
164    private void registerPool(final String username, final String password)
165            throws NamingException, SQLException {
166
167        final ConnectionPoolDataSource cpds = testCPDS(username, password);
168
169        // Create an object pool to contain our PooledConnections
170        factory = new KeyedCPDSConnectionFactory(cpds, getValidationQuery(),
171                getValidationQueryTimeout(), isRollbackAfterValidation());
172        factory.setMaxConnLifetimeMillis(getMaxConnLifetimeMillis());
173
174        final GenericKeyedObjectPoolConfig config =
175                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(getDefaultMaxWaitMillis());
183        config.setMinEvictableIdleTimeMillis(
184                getDefaultMinEvictableIdleTimeMillis());
185        config.setMinIdlePerKey(getDefaultMinIdle());
186        config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun());
187        config.setSoftMinEvictableIdleTimeMillis(
188                getDefaultSoftMinEvictableIdleTimeMillis());
189        config.setTestOnCreate(getDefaultTestOnCreate());
190        config.setTestOnBorrow(getDefaultTestOnBorrow());
191        config.setTestOnReturn(getDefaultTestOnReturn());
192        config.setTestWhileIdle(getDefaultTestWhileIdle());
193        config.setTimeBetweenEvictionRunsMillis(
194                getDefaultTimeBetweenEvictionRunsMillis());
195
196        final KeyedObjectPool<UserPassKey,PooledConnectionAndInfo> tmpPool =
197                new GenericKeyedObjectPool<>(factory, config);
198        factory.setPool(tmpPool);
199        pool = tmpPool;
200    }
201
202    @Override
203    protected void setupDefaults(final Connection con, final String username) throws SQLException {
204        final Boolean defaultAutoCommit = isDefaultAutoCommit();
205        if (defaultAutoCommit != null &&
206                con.getAutoCommit() != defaultAutoCommit.booleanValue()) {
207            con.setAutoCommit(defaultAutoCommit.booleanValue());
208        }
209
210        final int defaultTransactionIsolation = getDefaultTransactionIsolation();
211        if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
212            con.setTransactionIsolation(defaultTransactionIsolation);
213        }
214
215        final Boolean defaultReadOnly = isDefaultReadOnly();
216        if (defaultReadOnly != null &&
217                con.isReadOnly() != defaultReadOnly.booleanValue()) {
218            con.setReadOnly(defaultReadOnly.booleanValue());
219        }
220    }
221
222    /**
223     * Supports Serialization interface.
224     *
225     * @param in a <code>java.io.ObjectInputStream</code> value
226     * @throws IOException if an error occurs
227     * @throws ClassNotFoundException if an error occurs
228     */
229    private void readObject(final ObjectInputStream in)
230        throws IOException, ClassNotFoundException {
231        try
232        {
233            in.defaultReadObject();
234            final SharedPoolDataSource oldDS = (SharedPoolDataSource)
235                new SharedPoolDataSourceFactory()
236                    .getObjectInstance(getReference(), null, null, null);
237            this.pool = oldDS.pool;
238        }
239        catch (final NamingException e)
240        {
241            throw new IOException("NamingException: " + e);
242        }
243    }
244}
245