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