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.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 }