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.KeyedObjectPool;
34 import org.apache.commons.pool.KeyedPoolableObjectFactory;
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 KeyedCPDSConnectionFactory
44 implements KeyedPoolableObjectFactory, 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 KeyedObjectPool _pool;
54
55 /**
56 * Map of PooledConnections for which close events are ignored.
57 * Connections are muted when they are being validated.
58 */
59 private final Map /* <PooledConnection, null> */ validatingMap = new HashMap();
60
61 /**
62 * Map of PooledConnectionAndInfo instances
63 */
64 private final WeakHashMap /* <PooledConnection, PooledConnectionAndInfo> */ pcMap = new WeakHashMap();
65
66 /**
67 * Create a new <tt>KeyedPoolableConnectionFactory</tt>.
68 * @param cpds the ConnectionPoolDataSource from which to obtain PooledConnection's
69 * @param pool the {*link ObjectPool} in which to pool those {*link Connection}s
70 * @param validationQuery a query to use to {*link #validateObject validate} {*link Connection}s.
71 * Should return at least one row. May be <tt>null</tt>
72 */
73 public KeyedCPDSConnectionFactory(ConnectionPoolDataSource cpds,
74 KeyedObjectPool pool,
75 String validationQuery) {
76 this(cpds , pool, validationQuery, false);
77 }
78
79 /**
80 * Create a new <tt>KeyedPoolableConnectionFactory</tt>.
81 * @param cpds the ConnectionPoolDataSource from which to obtain
82 * PooledConnections
83 * @param pool the {@link KeyedObjectPool} in which to pool those
84 * {@link Connection}s
85 * @param validationQuery a query to use to {@link #validateObject validate}
86 * {@link Connection}s. Should return at least one row. May be <tt>null</tt>
87 * @param rollbackAfterValidation whether a rollback should be issued after
88 * {@link #validateObject validating} {@link Connection}s.
89 */
90 public KeyedCPDSConnectionFactory(ConnectionPoolDataSource cpds,
91 KeyedObjectPool pool,
92 String validationQuery,
93 boolean rollbackAfterValidation) {
94 _cpds = cpds;
95 _pool = pool;
96 pool.setFactory(this);
97 _validationQuery = validationQuery;
98 _rollbackAfterValidation = rollbackAfterValidation;
99 }
100
101 /**
102 * Returns the keyed object pool used to pool connections created by this factory.
103 *
104 * @return KeyedObjectPool managing pooled connections
105 */
106 public KeyedObjectPool getPool() {
107 return _pool;
108 }
109
110 /**
111 * Creates a new {@link PooledConnectionAndInfo} from the given {@link UserPassKey}.
112 *
113 * @param key {@link UserPassKey} containing user credentials
114 * @throws SQLException if the connection could not be created.
115 * @see org.apache.commons.pool.KeyedPoolableObjectFactory#makeObject(java.lang.Object)
116 */
117 public synchronized Object makeObject(Object key) throws Exception {
118 Object obj = null;
119 UserPassKey upkey = (UserPassKey)key;
120
121 PooledConnection pc = null;
122 String username = upkey.getUsername();
123 String password = upkey.getPassword();
124 if (username == null) {
125 pc = _cpds.getPooledConnection();
126 } else {
127 pc = _cpds.getPooledConnection(username, password);
128 }
129
130 if (pc == null) {
131 throw new IllegalStateException("Connection pool data source returned null from getPooledConnection");
132 }
133
134 // should we add this object as a listener or the pool.
135 // consider the validateObject method in decision
136 pc.addConnectionEventListener(this);
137 obj = new PooledConnectionAndInfo(pc, username, password);
138 pcMap.put(pc, obj);
139
140 return obj;
141 }
142
143 /**
144 * Closes the PooledConnection and stops listening for events from it.
145 */
146 public void destroyObject(Object key, Object obj) throws Exception {
147 if (obj instanceof PooledConnectionAndInfo) {
148 PooledConnection pc = ((PooledConnectionAndInfo)obj).getPooledConnection();
149 pc.removeConnectionEventListener(this);
150 pcMap.remove(pc);
151 pc.close();
152 }
153 }
154
155 /**
156 * Validates a pooled connection.
157 *
158 * @param key ignored
159 * @param obj {@link PooledConnectionAndInfo} containing the connection to validate
160 * @return true if validation suceeds
161 */
162 public boolean validateObject(Object key, Object obj) {
163 boolean valid = false;
164 if (obj instanceof PooledConnectionAndInfo) {
165 PooledConnection pconn =
166 ((PooledConnectionAndInfo)obj).getPooledConnection();
167 String query = _validationQuery;
168 if (null != query) {
169 Connection conn = null;
170 Statement stmt = null;
171 ResultSet rset = null;
172 // logical Connection from the PooledConnection must be closed
173 // before another one can be requested and closing it will
174 // generate an event. Keep track so we know not to return
175 // the PooledConnection
176 validatingMap.put(pconn, null);
177 try {
178 conn = pconn.getConnection();
179 stmt = conn.createStatement();
180 rset = stmt.executeQuery(query);
181 if (rset.next()) {
182 valid = true;
183 } else {
184 valid = false;
185 }
186 if (_rollbackAfterValidation) {
187 conn.rollback();
188 }
189 } catch(Exception e) {
190 valid = false;
191 } finally {
192 if (rset != null) {
193 try {
194 rset.close();
195 } catch (Throwable t) {
196 // ignore
197 }
198 }
199 if (stmt != null) {
200 try {
201 stmt.close();
202 } catch (Throwable t) {
203 // ignore
204 }
205 }
206 if (conn != null) {
207 try {
208 conn.close();
209 } catch (Throwable t) {
210 // ignore
211 }
212 }
213 validatingMap.remove(pconn);
214 }
215 } else {
216 valid = true;
217 }
218 } else {
219 valid = false;
220 }
221 return valid;
222 }
223
224 public void passivateObject(Object key, Object obj) {
225 }
226
227 public void activateObject(Object key, Object obj) {
228 }
229
230 // ***********************************************************************
231 // java.sql.ConnectionEventListener implementation
232 // ***********************************************************************
233
234 /**
235 * This will be called if the Connection returned by the getConnection
236 * method came from a PooledConnection, and the user calls the close()
237 * method of this connection object. What we need to do here is to
238 * release this PooledConnection from our pool...
239 */
240 public void connectionClosed(ConnectionEvent event) {
241 PooledConnection pc = (PooledConnection)event.getSource();
242 // if this event occurred because we were validating, or if this
243 // connection has been marked for removal, ignore it
244 // otherwise return the connection to the pool.
245 if (!validatingMap.containsKey(pc)) {
246 PooledConnectionAndInfo info =
247 (PooledConnectionAndInfo) pcMap.get(pc);
248 if (info == null) {
249 throw new IllegalStateException(NO_KEY_MESSAGE);
250 }
251 try {
252 _pool.returnObject(info.getUserPassKey(), info);
253 } catch (Exception e) {
254 System.err.println("CLOSING DOWN CONNECTION AS IT COULD " +
255 "NOT BE RETURNED TO THE POOL");
256 pc.removeConnectionEventListener(this);
257 try {
258 _pool.invalidateObject(info.getUserPassKey(), info);
259 } catch (Exception e3) {
260 System.err.println("EXCEPTION WHILE DESTROYING OBJECT " +
261 info);
262 e3.printStackTrace();
263 }
264 }
265 }
266 }
267
268 /**
269 * If a fatal error occurs, close the underlying physical connection so as
270 * not to be returned in the future
271 */
272 public void connectionErrorOccurred(ConnectionEvent event) {
273 PooledConnection pc = (PooledConnection)event.getSource();
274 if (null != event.getSQLException()) {
275 System.err
276 .println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" +
277 event.getSQLException() + ")");
278 }
279 pc.removeConnectionEventListener(this);
280
281 PooledConnectionAndInfo info = (PooledConnectionAndInfo) pcMap.get(pc);
282 if (info == null) {
283 throw new IllegalStateException(NO_KEY_MESSAGE);
284 }
285 try {
286 _pool.invalidateObject(info.getUserPassKey(), info);
287 } catch (Exception e) {
288 System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info);
289 e.printStackTrace();
290 }
291 }
292
293 // ***********************************************************************
294 // PooledConnectionManager implementation
295 // ***********************************************************************
296
297 /**
298 * Invalidates the PooledConnection in the pool. The KeyedCPDSConnectionFactory
299 * closes the connection and pool counters are updated appropriately.
300 * Also clears any idle instances associated with the username that was used
301 * to create the PooledConnection. Connections associated with this user
302 * are not affected and they will not be automatically closed on return to the pool.
303 */
304 public void invalidate(PooledConnection pc) throws SQLException {
305 PooledConnectionAndInfo info = (PooledConnectionAndInfo) pcMap.get(pc);
306 if (info == null) {
307 throw new IllegalStateException(NO_KEY_MESSAGE);
308 }
309 UserPassKey key = info.getUserPassKey();
310 try {
311 _pool.invalidateObject(key, info); // Destroy and update pool counters
312 _pool.clear(key); // Remove any idle instances with this key
313 } catch (Exception ex) {
314 throw (SQLException) new SQLException("Error invalidating connection").initCause(ex);
315 }
316 }
317
318 /**
319 * Does nothing. This factory does not cache user credentials.
320 */
321 public void setPassword(String password) {
322 }
323
324 /**
325 * This implementation does not fully close the KeyedObjectPool, as
326 * this would affect all users. Instead, it clears the pool associated
327 * with the given user. This method is not currently used.
328 */
329 public void closePool(String username) throws SQLException {
330 try {
331 _pool.clear(new UserPassKey(username, null));
332 } catch (Exception ex) {
333 throw (SQLException) new SQLException("Error closing connection pool").initCause(ex);
334 }
335 }
336
337 }