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
018 package org.apache.commons.dbcp.datasources;
019
020 import java.io.IOException;
021 import java.io.ObjectInputStream;
022 import java.sql.Connection;
023 import java.sql.SQLException;
024
025 import javax.naming.NamingException;
026 import javax.naming.Reference;
027 import javax.naming.StringRefAddr;
028 import javax.sql.ConnectionPoolDataSource;
029
030 import org.apache.commons.pool.KeyedObjectPool;
031 import org.apache.commons.pool.impl.GenericKeyedObjectPool;
032 import org.apache.commons.pool.impl.GenericObjectPool;
033 import org.apache.commons.dbcp.SQLNestedException;
034
035 /**
036 * <p>A pooling <code>DataSource</code> appropriate for deployment within
037 * J2EE environment. There are many configuration options, most of which are
038 * defined in the parent class. All users (based on username) share a single
039 * maximum number of Connections in this datasource.</p>
040 *
041 * <p>User passwords can be changed without re-initializing the datasource.
042 * When a <code>getConnection(username, password)</code> request is processed
043 * with a password that is different from those used to create connections in the
044 * pool associated with <code>username</code>, an attempt is made to create a
045 * new connection using the supplied password and if this succeeds, idle connections
046 * created using the old password are destroyed and new connections are created
047 * using the new password.</p>
048 *
049 * @author John D. McNally
050 * @version $Revision: 892307 $ $Date: 2013-12-31 23:27:28 +0000 (Tue, 31 Dec 2013) $
051 */
052 public class SharedPoolDataSource
053 extends InstanceKeyDataSource {
054
055 private static final long serialVersionUID = -8132305535403690372L;
056
057 private int maxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE;
058 private int maxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;
059 private int maxWait = (int)Math.min(Integer.MAX_VALUE,
060 GenericObjectPool.DEFAULT_MAX_WAIT);
061 private transient KeyedObjectPool pool = null;
062 private transient KeyedCPDSConnectionFactory factory = null;
063
064 /**
065 * Default no-arg constructor for Serialization
066 */
067 public SharedPoolDataSource() {
068 }
069
070 /**
071 * Close pool being maintained by this datasource.
072 */
073 public void close() throws Exception {
074 if (pool != null) {
075 pool.close();
076 }
077 InstanceKeyObjectFactory.removeInstance(instanceKey);
078 }
079
080 // -------------------------------------------------------------------
081 // Properties
082
083 /**
084 * The maximum number of active connections that can be allocated from
085 * this pool at the same time, or non-positive for no limit.
086 */
087 public int getMaxActive() {
088 return (this.maxActive);
089 }
090
091 /**
092 * The maximum number of active connections that can be allocated from
093 * this pool at the same time, or non-positive for no limit.
094 * The default is 8.
095 */
096 public void setMaxActive(int maxActive) {
097 assertInitializationAllowed();
098 this.maxActive = maxActive;
099 }
100
101 /**
102 * The maximum number of active connections that can remain idle in the
103 * pool, without extra ones being released, or negative for no limit.
104 */
105 public int getMaxIdle() {
106 return (this.maxIdle);
107 }
108
109 /**
110 * The maximum number of active connections that can remain idle in the
111 * pool, without extra ones being released, or negative for no limit.
112 * The default is 8.
113 */
114 public void setMaxIdle(int maxIdle) {
115 assertInitializationAllowed();
116 this.maxIdle = maxIdle;
117 }
118
119 /**
120 * The maximum number of milliseconds that the pool will wait (when there
121 * are no available connections) for a connection to be returned before
122 * throwing an exception, or -1 to wait indefinitely. Will fail
123 * immediately if value is 0.
124 * The default is -1.
125 */
126 public int getMaxWait() {
127 return (this.maxWait);
128 }
129
130 /**
131 * The maximum number of milliseconds that the pool will wait (when there
132 * are no available connections) for a connection to be returned before
133 * throwing an exception, or -1 to wait indefinitely. Will fail
134 * immediately if value is 0.
135 * The default is -1.
136 */
137 public void setMaxWait(int maxWait) {
138 assertInitializationAllowed();
139 this.maxWait = maxWait;
140 }
141
142 // ----------------------------------------------------------------------
143 // Instrumentation Methods
144
145 /**
146 * Get the number of active connections in the pool.
147 */
148 public int getNumActive() {
149 return (pool == null) ? 0 : pool.getNumActive();
150 }
151
152 /**
153 * Get the number of idle connections in the pool.
154 */
155 public int getNumIdle() {
156 return (pool == null) ? 0 : pool.getNumIdle();
157 }
158
159 // ----------------------------------------------------------------------
160 // Inherited abstract methods
161
162 protected PooledConnectionAndInfo
163 getPooledConnectionAndInfo(String username, String password)
164 throws SQLException {
165
166 synchronized(this) {
167 if (pool == null) {
168 try {
169 registerPool(username, password);
170 } catch (NamingException e) {
171 throw new SQLNestedException("RegisterPool failed", e);
172 }
173 }
174 }
175
176 PooledConnectionAndInfo info = null;
177
178 UserPassKey key = new UserPassKey(username, password);
179
180 try {
181 info = (PooledConnectionAndInfo) pool.borrowObject(key);
182 }
183 catch (Exception e) {
184 throw new SQLNestedException(
185 "Could not retrieve connection info from pool", e);
186 }
187 return info;
188 }
189
190 protected PooledConnectionManager getConnectionManager(UserPassKey upkey) {
191 return factory;
192 }
193
194 /**
195 * Returns a <code>SharedPoolDataSource</code> {@link Reference}.
196 *
197 * @since 1.2.2
198 */
199 public Reference getReference() throws NamingException {
200 Reference ref = new Reference(getClass().getName(),
201 SharedPoolDataSourceFactory.class.getName(), null);
202 ref.add(new StringRefAddr("instanceKey", instanceKey));
203 return ref;
204 }
205
206 private void registerPool(
207 String username, String password)
208 throws javax.naming.NamingException, SQLException {
209
210 ConnectionPoolDataSource cpds = testCPDS(username, password);
211
212 // Create an object pool to contain our PooledConnections
213 GenericKeyedObjectPool tmpPool = new GenericKeyedObjectPool(null);
214 tmpPool.setMaxActive(getMaxActive());
215 tmpPool.setMaxIdle(getMaxIdle());
216 tmpPool.setMaxWait(getMaxWait());
217 tmpPool.setWhenExhaustedAction(whenExhaustedAction(maxActive, maxWait));
218 tmpPool.setTestOnBorrow(getTestOnBorrow());
219 tmpPool.setTestOnReturn(getTestOnReturn());
220 tmpPool.setTimeBetweenEvictionRunsMillis(
221 getTimeBetweenEvictionRunsMillis());
222 tmpPool.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
223 tmpPool.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis());
224 tmpPool.setTestWhileIdle(getTestWhileIdle());
225 pool = tmpPool;
226 // Set up the factory we will use (passing the pool associates
227 // the factory with the pool, so we do not have to do so
228 // explicitly)
229 factory = new KeyedCPDSConnectionFactory(cpds, pool, getValidationQuery(),
230 isRollbackAfterValidation());
231 }
232
233 protected void setupDefaults(Connection con, String username) throws SQLException {
234 boolean defaultAutoCommit = isDefaultAutoCommit();
235 if (con.getAutoCommit() != defaultAutoCommit) {
236 con.setAutoCommit(defaultAutoCommit);
237 }
238
239 int defaultTransactionIsolation = getDefaultTransactionIsolation();
240 if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
241 con.setTransactionIsolation(defaultTransactionIsolation);
242 }
243
244 boolean defaultReadOnly = isDefaultReadOnly();
245 if (con.isReadOnly() != defaultReadOnly) {
246 con.setReadOnly(defaultReadOnly);
247 }
248 }
249
250 /**
251 * Supports Serialization interface.
252 *
253 * @param in a <code>java.io.ObjectInputStream</code> value
254 * @exception IOException if an error occurs
255 * @exception ClassNotFoundException if an error occurs
256 */
257 private void readObject(ObjectInputStream in)
258 throws IOException, ClassNotFoundException {
259 try
260 {
261 in.defaultReadObject();
262 SharedPoolDataSource oldDS = (SharedPoolDataSource)
263 new SharedPoolDataSourceFactory()
264 .getObjectInstance(getReference(), null, null, null);
265 this.pool = oldDS.pool;
266 }
267 catch (NamingException e)
268 {
269 throw new IOException("NamingException: " + e);
270 }
271 }
272 }
273