View Javadoc

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 }