1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.dbcp.datasources;
19
20 import java.sql.Connection;
21 import java.sql.ResultSet;
22 import java.sql.SQLException;
23 import java.sql.Statement;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.WeakHashMap;
27
28 import javax.sql.ConnectionEvent;
29 import javax.sql.ConnectionEventListener;
30 import javax.sql.ConnectionPoolDataSource;
31 import javax.sql.PooledConnection;
32
33 import org.apache.commons.pool.ObjectPool;
34 import org.apache.commons.pool.PoolableObjectFactory;
35
36 /**
37 * A {@link PoolableObjectFactory} that creates
38 * {@link PoolableConnection}s.
39 *
40 * @author John D. McNally
41 * @version $Revision: 1023401 $ $Date: 2010-10-16 21:54:24 -0400 (Sat, 16 Oct 2010) $
42 */
43 class CPDSConnectionFactory
44 implements PoolableObjectFactory, ConnectionEventListener, PooledConnectionManager {
45
46 private static final String NO_KEY_MESSAGE
47 = "close() was called on a Connection, but "
48 + "I have no record of the underlying PooledConnection.";
49
50 private final ConnectionPoolDataSource _cpds;
51 private final String _validationQuery;
52 private final boolean _rollbackAfterValidation;
53 private final ObjectPool _pool;
54 private String _username = null;
55 private String _password = null;
56
57 /**
58 * Map of PooledConnections for which close events are ignored.
59 * Connections are muted when they are being validated.
60 */
61 private final Map /* <PooledConnection, null> */ validatingMap = new HashMap();
62
63 /**
64 * Map of PooledConnectionAndInfo instances
65 */
66 private final WeakHashMap /* <PooledConnection, PooledConnectionAndInfo> */ pcMap = new WeakHashMap();
67
68 /**
69 * Create a new <tt>PoolableConnectionFactory</tt>.
70 *
71 * @param cpds the ConnectionPoolDataSource from which to obtain
72 * PooledConnection's
73 * @param pool the {@link ObjectPool} in which to pool those
74 * {@link Connection}s
75 * @param validationQuery a query to use to {@link #validateObject validate}
76 * {@link Connection}s. Should return at least one row. May be
77 * <tt>null</tt>
78 * @param username
79 * @param password
80 */
81 public CPDSConnectionFactory(ConnectionPoolDataSource cpds,
82 ObjectPool pool,
83 String validationQuery,
84 String username,
85 String password) {
86 this(cpds, pool, validationQuery, false, username, password);
87 }
88
89 /**
90 * Create a new <tt>PoolableConnectionFactory</tt>.
91 *
92 * @param cpds the ConnectionPoolDataSource from which to obtain
93 * PooledConnection's
94 * @param pool the {@link ObjectPool} in which to pool those {@link
95 * Connection}s
96 * @param validationQuery a query to use to {@link #validateObject
97 * validate} {@link Connection}s. Should return at least one row.
98 * May be <tt>null</tt>
99 * @param rollbackAfterValidation whether a rollback should be issued
100 * after {@link #validateObject validating} {@link Connection}s.
101 * @param username
102 * @param password
103 */
104 public CPDSConnectionFactory(ConnectionPoolDataSource cpds,
105 ObjectPool pool,
106 String validationQuery,
107 boolean rollbackAfterValidation,
108 String username,
109 String password) {
110 _cpds = cpds;
111 _pool = pool;
112 pool.setFactory(this);
113 _validationQuery = validationQuery;
114 _username = username;
115 _password = password;
116 _rollbackAfterValidation = rollbackAfterValidation;
117 }
118
119 /**
120 * Returns the object pool used to pool connections created by this factory.
121 *
122 * @return ObjectPool managing pooled connections
123 */
124 public ObjectPool getPool() {
125 return _pool;
126 }
127
128 public synchronized Object makeObject() {
129 Object obj;
130 try {
131 PooledConnection pc = null;
132 if (_username == null) {
133 pc = _cpds.getPooledConnection();
134 } else {
135 pc = _cpds.getPooledConnection(_username, _password);
136 }
137
138 if (pc == null) {
139 throw new IllegalStateException("Connection pool data source returned null from getPooledConnection");
140 }
141
142 // should we add this object as a listener or the pool.
143 // consider the validateObject method in decision
144 pc.addConnectionEventListener(this);
145 obj = new PooledConnectionAndInfo(pc, _username, _password);
146 pcMap.put(pc, obj);
147 } catch (SQLException e) {
148 throw new RuntimeException(e.getMessage());
149 }
150 return obj;
151 }
152
153 /**
154 * Closes the PooledConnection and stops listening for events from it.
155 */
156 public void destroyObject(Object obj) throws Exception {
157 if (obj instanceof PooledConnectionAndInfo) {
158 PooledConnection pc = ((PooledConnectionAndInfo)obj).getPooledConnection();
159 pc.removeConnectionEventListener(this);
160 pcMap.remove(pc);
161 pc.close();
162 }
163 }
164
165 public boolean validateObject(Object obj) {
166 boolean valid = false;
167 if (obj instanceof PooledConnectionAndInfo) {
168 PooledConnection pconn =
169 ((PooledConnectionAndInfo) obj).getPooledConnection();
170 String query = _validationQuery;
171 if (null != query) {
172 Connection conn = null;
173 Statement stmt = null;
174 ResultSet rset = null;
175 // logical Connection from the PooledConnection must be closed
176 // before another one can be requested and closing it will
177 // generate an event. Keep track so we know not to return
178 // the PooledConnection
179 validatingMap.put(pconn, null);
180 try {
181 conn = pconn.getConnection();
182 stmt = conn.createStatement();
183 rset = stmt.executeQuery(query);
184 if (rset.next()) {
185 valid = true;
186 } else {
187 valid = false;
188 }
189 if (_rollbackAfterValidation) {
190 conn.rollback();
191 }
192 } catch (Exception e) {
193 valid = false;
194 } finally {
195 if (rset != null) {
196 try {
197 rset.close();
198 } catch (Throwable t) {
199 // ignore
200 }
201 }
202 if (stmt != null) {
203 try {
204 stmt.close();
205 } catch (Throwable t) {
206 // ignore
207 }
208 }
209 if (conn != null) {
210 try {
211 conn.close();
212 } catch (Throwable t) {
213 // ignore
214 }
215 }
216 validatingMap.remove(pconn);
217 }
218 } else {
219 valid = true;
220 }
221 } else {
222 valid = false;
223 }
224 return valid;
225 }
226
227 public void passivateObject(Object obj) {
228 }
229
230 public void activateObject(Object obj) {
231 }
232
233 // ***********************************************************************
234 // java.sql.ConnectionEventListener implementation
235 // ***********************************************************************
236
237 /**
238 * This will be called if the Connection returned by the getConnection
239 * method came from a PooledConnection, and the user calls the close()
240 * method of this connection object. What we need to do here is to
241 * release this PooledConnection from our pool...
242 */
243 public void connectionClosed(ConnectionEvent event) {
244 PooledConnection pc = (PooledConnection) event.getSource();
245 // if this event occured becase we were validating, ignore it
246 // otherwise return the connection to the pool.
247 if (!validatingMap.containsKey(pc)) {
248 Object info = pcMap.get(pc);
249 if (info == null) {
250 throw new IllegalStateException(NO_KEY_MESSAGE);
251 }
252
253 try {
254 _pool.returnObject(info);
255 } catch (Exception e) {
256 System.err.println("CLOSING DOWN CONNECTION AS IT COULD "
257 + "NOT BE RETURNED TO THE POOL");
258 pc.removeConnectionEventListener(this);
259 try {
260 destroyObject(info);
261 } catch (Exception e2) {
262 System.err.println("EXCEPTION WHILE DESTROYING OBJECT "
263 + info);
264 e2.printStackTrace();
265 }
266 }
267 }
268 }
269
270 /**
271 * If a fatal error occurs, close the underlying physical connection so as
272 * not to be returned in the future
273 */
274 public void connectionErrorOccurred(ConnectionEvent event) {
275 PooledConnection pc = (PooledConnection)event.getSource();
276 if (null != event.getSQLException()) {
277 System.err.println(
278 "CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR ("
279 + event.getSQLException() + ")");
280 }
281 pc.removeConnectionEventListener(this);
282
283 Object info = pcMap.get(pc);
284 if (info == null) {
285 throw new IllegalStateException(NO_KEY_MESSAGE);
286 }
287 try {
288 _pool.invalidateObject(info);
289 } catch (Exception e) {
290 System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info);
291 e.printStackTrace();
292 }
293 }
294
295 // ***********************************************************************
296 // PooledConnectionManager implementation
297 // ***********************************************************************
298
299 /**
300 * Invalidates the PooledConnection in the pool. The CPDSConnectionFactory
301 * closes the connection and pool counters are updated appropriately.
302 * Also closes the pool. This ensures that all idle connections are closed
303 * and connections that are checked out are closed on return.
304 */
305 public void invalidate(PooledConnection pc) throws SQLException {
306 Object info = pcMap.get(pc);
307 if (info == null) {
308 throw new IllegalStateException(NO_KEY_MESSAGE);
309 }
310 try {
311 _pool.invalidateObject(info); // Destroy instance and update pool counters
312 _pool.close(); // Clear any other instances in this pool and kill others as they come back
313 } catch (Exception ex) {
314 throw (SQLException) new SQLException("Error invalidating connection").initCause(ex);
315 }
316 }
317
318 /**
319 * Sets the database password used when creating new connections.
320 *
321 * @param password new password
322 */
323 public synchronized void setPassword(String password) {
324 _password = password;
325 }
326
327 /**
328 * Verifies that the username matches the user whose connections are being managed by this
329 * factory and closes the pool if this is the case; otherwise does nothing.
330 */
331 public void closePool(String username) throws SQLException {
332 synchronized (this) {
333 if (username == null || !username.equals(_username)) {
334 return;
335 }
336 }
337 try {
338 _pool.close();
339 } catch (Exception ex) {
340 throw (SQLException) new SQLException("Error closing connection pool").initCause(ex);
341 }
342 }
343
344 }