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 * @version $Id: SharedPoolDataSource.java 1649430 2015-01-04 21:29:32Z tn $
050 * @since 2.0
051 */
052public class SharedPoolDataSource extends InstanceKeyDataSource {
053
054    private static final long serialVersionUID = -1458539734480586454L;
055
056    // Pool properties
057    private int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
058
059
060    private transient KeyedObjectPool<UserPassKey,PooledConnectionAndInfo> pool = null;
061    private transient KeyedCPDSConnectionFactory factory = null;
062
063    /**
064     * Default no-arg constructor for Serialization
065     */
066    public SharedPoolDataSource() {
067    }
068
069    /**
070     * Close pool being maintained by this datasource.
071     */
072    @Override
073    public void close() throws Exception {
074        if (pool != null) {
075            pool.close();
076        }
077        InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
078    }
079
080
081    // -------------------------------------------------------------------
082    // Properties
083
084    /**
085     * Set {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
086     */
087    public int getMaxTotal() {
088        return this.maxTotal;
089    }
090
091    /**
092     * Get {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
093     */
094    public void setMaxTotal(int maxTotal) {
095        assertInitializationAllowed();
096        this.maxTotal = maxTotal;
097    }
098
099
100    // ----------------------------------------------------------------------
101    // Instrumentation Methods
102
103    /**
104     * Get the number of active connections in the pool.
105     */
106    public int getNumActive() {
107        return pool == null ? 0 : pool.getNumActive();
108    }
109
110    /**
111     * Get the number of idle connections in the pool.
112     */
113    public int getNumIdle() {
114        return pool == null ? 0 : pool.getNumIdle();
115    }
116
117    // ----------------------------------------------------------------------
118    // Inherited abstract methods
119
120    @Override
121    protected PooledConnectionAndInfo
122        getPooledConnectionAndInfo(String username, String password)
123        throws SQLException {
124
125        synchronized(this) {
126            if (pool == null) {
127                try {
128                    registerPool(username, password);
129                } catch (NamingException e) {
130                    throw new SQLException("RegisterPool failed", e);
131                }
132            }
133        }
134
135        PooledConnectionAndInfo info = null;
136
137        UserPassKey key = new UserPassKey(username, password);
138
139        try {
140            info = pool.borrowObject(key);
141        }
142        catch (Exception e) {
143            throw new SQLException(
144                    "Could not retrieve connection info from pool", e);
145        }
146        return info;
147    }
148
149    @Override
150    protected PooledConnectionManager getConnectionManager(UserPassKey upkey)  {
151        return factory;
152    }
153
154    /**
155     * Returns a <code>SharedPoolDataSource</code> {@link Reference}.
156     */
157    @Override
158    public Reference getReference() throws NamingException {
159        Reference ref = new Reference(getClass().getName(),
160            SharedPoolDataSourceFactory.class.getName(), null);
161        ref.add(new StringRefAddr("instanceKey", getInstanceKey()));
162        return ref;
163    }
164
165    private void registerPool(String username, String password)
166            throws NamingException, SQLException {
167
168        ConnectionPoolDataSource cpds = testCPDS(username, password);
169
170        // Create an object pool to contain our PooledConnections
171        factory = new KeyedCPDSConnectionFactory(cpds, getValidationQuery(),
172                getValidationQueryTimeout(), isRollbackAfterValidation());
173        factory.setMaxConnLifetimeMillis(getMaxConnLifetimeMillis());
174
175        GenericKeyedObjectPoolConfig config =
176                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(
185                getDefaultMinEvictableIdleTimeMillis());
186        config.setMinIdlePerKey(getDefaultMinIdle());
187        config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun());
188        config.setSoftMinEvictableIdleTimeMillis(
189                getDefaultSoftMinEvictableIdleTimeMillis());
190        config.setTestOnCreate(getDefaultTestOnCreate());
191        config.setTestOnBorrow(getDefaultTestOnBorrow());
192        config.setTestOnReturn(getDefaultTestOnReturn());
193        config.setTestWhileIdle(getDefaultTestWhileIdle());
194        config.setTimeBetweenEvictionRunsMillis(
195                getDefaultTimeBetweenEvictionRunsMillis());
196
197        KeyedObjectPool<UserPassKey,PooledConnectionAndInfo> tmpPool =
198                new GenericKeyedObjectPool<>(factory, config);
199        factory.setPool(tmpPool);
200        pool = tmpPool;
201    }
202
203    @Override
204    protected void setupDefaults(Connection con, String username) throws SQLException {
205        Boolean defaultAutoCommit = isDefaultAutoCommit();
206        if (defaultAutoCommit != null &&
207                con.getAutoCommit() != defaultAutoCommit.booleanValue()) {
208            con.setAutoCommit(defaultAutoCommit.booleanValue());
209        }
210
211        int defaultTransactionIsolation = getDefaultTransactionIsolation();
212        if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
213            con.setTransactionIsolation(defaultTransactionIsolation);
214        }
215
216        Boolean defaultReadOnly = isDefaultReadOnly();
217        if (defaultReadOnly != null &&
218                con.isReadOnly() != defaultReadOnly.booleanValue()) {
219            con.setReadOnly(defaultReadOnly.booleanValue());
220        }
221    }
222
223    /**
224     * Supports Serialization interface.
225     *
226     * @param in a <code>java.io.ObjectInputStream</code> value
227     * @exception IOException if an error occurs
228     * @exception ClassNotFoundException if an error occurs
229     */
230    private void readObject(ObjectInputStream in)
231        throws IOException, ClassNotFoundException {
232        try
233        {
234            in.defaultReadObject();
235            SharedPoolDataSource oldDS = (SharedPoolDataSource)
236                new SharedPoolDataSourceFactory()
237                    .getObjectInstance(getReference(), null, null, null);
238            this.pool = oldDS.pool;
239        }
240        catch (NamingException e)
241        {
242            throw new IOException("NamingException: " + e);
243        }
244    }
245}
246