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 * https://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; 028import javax.sql.DataSource; 029 030import org.apache.commons.dbcp2.PoolableConnection; 031import org.apache.commons.pool2.KeyedObjectPool; 032import org.apache.commons.pool2.KeyedPooledObjectFactory; 033import org.apache.commons.pool2.impl.GenericKeyedObjectPool; 034import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; 035 036/** 037 * <p> 038 * A pooling {@link DataSource} appropriate for deployment within J2EE environment. There are many configuration 039 * options, most of which are defined in the parent class. All users (based on user name) share a single maximum number 040 * of Connections in this data source. 041 * </p> 042 * 043 * <p> 044 * User passwords can be changed without re-initializing the data source. When a 045 * {@code getConnection(user name, password)} request is processed with a password that is different from those 046 * used to create connections in the pool associated with {@code user name}, an attempt is made to create a new 047 * connection using the supplied password and if this succeeds, idle connections created using the old password are 048 * destroyed and new connections are created using the new password. 049 * </p> 050 * 051 * @since 2.0 052 */ 053public class SharedPoolDataSource extends InstanceKeyDataSource { 054 055 private static final long serialVersionUID = -1458539734480586454L; 056 057 /** 058 * Max total defaults to {@link GenericKeyedObjectPoolConfig#DEFAULT_MAX_TOTAL}. 059 */ 060 private volatile int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL; 061 062 /** 063 * Maps user credentials to pooled connection with credentials. 064 */ 065 private transient KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool; 066 067 /** 068 * A {@link KeyedPooledObjectFactory} that creates {@link PoolableConnection}s. 069 */ 070 private transient KeyedCPDSConnectionFactory factory; 071 072 /** 073 * Default no-argument constructor for Serialization 074 */ 075 public SharedPoolDataSource() { 076 // empty. 077 } 078 079 /** 080 * Closes pool being maintained by this data source. 081 */ 082 @Override 083 public void close() throws SQLException { 084 if (pool != null) { 085 pool.close(); 086 } 087 InstanceKeyDataSourceFactory.removeInstance(getInstanceKey()); 088 } 089 090 @Override 091 protected PooledConnectionManager getConnectionManager(final UserPassKey userPassKey) { 092 return factory; 093 } 094 095 /** 096 * Gets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. 097 * 098 * @return {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. 099 */ 100 public int getMaxTotal() { 101 return this.maxTotal; 102 } 103 104 /** 105 * Gets the number of active connections in the pool. 106 * 107 * @return The number of active connections in the pool. 108 */ 109 public int getNumActive() { 110 return pool == null ? 0 : pool.getNumActive(); 111 } 112 113 /** 114 * Gets the number of idle connections in the pool. 115 * 116 * @return The number of idle connections in the pool. 117 */ 118 public int getNumIdle() { 119 return pool == null ? 0 : pool.getNumIdle(); 120 } 121 122 @Override 123 protected PooledConnectionAndInfo getPooledConnectionAndInfo(final String userName, final String userPassword) 124 throws SQLException { 125 126 synchronized (this) { 127 if (pool == null) { 128 try { 129 registerPool(userName, userPassword); 130 } catch (final NamingException e) { 131 throw new SQLException("registerPool failed", e); 132 } 133 } 134 } 135 136 try { 137 return pool.borrowObject(new UserPassKey(userName, userPassword)); 138 } catch (final Exception e) { 139 throw new SQLException("Could not retrieve connection info from pool", e); 140 } 141 } 142 143 /** 144 * Creates a new {@link Reference} to a {@link SharedPoolDataSource}. 145 */ 146 @Override 147 public Reference getReference() throws NamingException { 148 final Reference ref = new Reference(getClass().getName(), SharedPoolDataSourceFactory.class.getName(), null); 149 ref.add(new StringRefAddr("instanceKey", getInstanceKey())); 150 return ref; 151 } 152 153 /** 154 * Deserializes an instance from an ObjectInputStream. 155 * 156 * @param in The source ObjectInputStream. 157 * @throws IOException Any of the usual Input/Output related exceptions. 158 * @throws ClassNotFoundException A class of a serialized object cannot be found. 159 */ 160 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 161 in.defaultReadObject(); 162 this.pool = readObjectImpl(); 163 } 164 165 private KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> readObjectImpl() throws IOException, ClassNotFoundException { 166 try { 167 return ((SharedPoolDataSource) new SharedPoolDataSourceFactory().getObjectInstance(getReference(), null, null, null)).pool; 168 } catch (final NamingException e) { 169 throw new IOException("NamingException: " + e); 170 } 171 } 172 173 private void registerPool(final String userName, final String password) throws NamingException, SQLException { 174 175 final ConnectionPoolDataSource cpds = testCPDS(userName, password); 176 177 // Create an object pool to contain our PooledConnections 178 factory = new KeyedCPDSConnectionFactory(cpds, getValidationQuery(), getValidationQueryTimeoutDuration(), isRollbackAfterValidation()); 179 factory.setMaxConn(getMaxConnDuration()); 180 181 final GenericKeyedObjectPoolConfig<PooledConnectionAndInfo> config = new GenericKeyedObjectPoolConfig<>(); 182 config.setBlockWhenExhausted(getDefaultBlockWhenExhausted()); 183 config.setEvictionPolicyClassName(getDefaultEvictionPolicyClassName()); 184 config.setLifo(getDefaultLifo()); 185 config.setMaxIdlePerKey(getDefaultMaxIdle()); 186 config.setMaxTotal(getMaxTotal()); 187 config.setMaxTotalPerKey(getDefaultMaxTotal()); 188 config.setMaxWait(getDefaultMaxWait()); 189 config.setMinEvictableIdleDuration(getDefaultMinEvictableIdleDuration()); 190 config.setMinIdlePerKey(getDefaultMinIdle()); 191 config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun()); 192 config.setSoftMinEvictableIdleDuration(getDefaultSoftMinEvictableIdleDuration()); 193 config.setTestOnCreate(getDefaultTestOnCreate()); 194 config.setTestOnBorrow(getDefaultTestOnBorrow()); 195 config.setTestOnReturn(getDefaultTestOnReturn()); 196 config.setTestWhileIdle(getDefaultTestWhileIdle()); 197 config.setTimeBetweenEvictionRuns(getDefaultDurationBetweenEvictionRuns()); 198 199 final KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> tmpPool = new GenericKeyedObjectPool<>(factory, config); 200 factory.setPool(tmpPool); 201 pool = tmpPool; 202 } 203 204 /** 205 * Sets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. 206 * 207 * @param maxTotal 208 * {@link GenericKeyedObjectPool#getMaxTotal()} for this pool. 209 */ 210 public void setMaxTotal(final int maxTotal) { 211 assertInitializationAllowed(); 212 this.maxTotal = maxTotal; 213 } 214 215 @Override 216 protected void setupDefaults(final Connection connection, final String userName) throws SQLException { 217 final Boolean defaultAutoCommit = isDefaultAutoCommit(); 218 if (defaultAutoCommit != null && connection.getAutoCommit() != defaultAutoCommit) { 219 connection.setAutoCommit(defaultAutoCommit); 220 } 221 222 final int defaultTransactionIsolation = getDefaultTransactionIsolation(); 223 if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) { 224 connection.setTransactionIsolation(defaultTransactionIsolation); 225 } 226 227 final Boolean defaultReadOnly = isDefaultReadOnly(); 228 if (defaultReadOnly != null && connection.isReadOnly() != defaultReadOnly) { 229 connection.setReadOnly(defaultReadOnly); 230 } 231 } 232 233 @Override 234 protected void toStringFields(final StringBuilder builder) { 235 super.toStringFields(builder); 236 builder.append(", maxTotal="); 237 builder.append(maxTotal); 238 } 239}