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