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