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