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