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