Coverage Report - org.apache.commons.dbcp.datasources.CPDSConnectionFactory
 
Classes in this File Line Coverage Branch Coverage Complexity
CPDSConnectionFactory
50%
63/124
32%
11/34
3.692
 
 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  88
     private String _username = null;
 55  88
     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  88
     private final Map /* <PooledConnection, null> */ validatingMap = new HashMap();
 62  
 
 63  
     /**
 64  
      * Map of PooledConnectionAndInfo instances
 65  
      */
 66  88
     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  2
         this(cpds, pool, validationQuery, false, username, password);
 87  2
     }
 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  88
                                   String password) {
 110  88
          _cpds = cpds;
 111  88
          _pool = pool;
 112  88
          pool.setFactory(this);
 113  88
          _validationQuery = validationQuery;
 114  88
          _username = username;
 115  88
          _password = password;
 116  88
          _rollbackAfterValidation = rollbackAfterValidation;
 117  88
      }
 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  4439
          return _pool;
 126  
      }
 127  
 
 128  
     public synchronized Object makeObject() {
 129  
         Object obj;
 130  
         try {
 131  298
             PooledConnection pc = null;
 132  298
             if (_username == null) {
 133  70
                 pc = _cpds.getPooledConnection();
 134  
             } else {
 135  228
                 pc = _cpds.getPooledConnection(_username, _password);
 136  
             }
 137  
 
 138  298
             if (pc == null) {
 139  0
                 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  298
             pc.addConnectionEventListener(this);
 145  298
             obj = new PooledConnectionAndInfo(pc, _username, _password);
 146  298
             pcMap.put(pc, obj);
 147  0
         } catch (SQLException e) {
 148  0
             throw new RuntimeException(e.getMessage());
 149  298
         }
 150  298
         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  64
         if (obj instanceof PooledConnectionAndInfo) {
 158  64
             PooledConnection pc = ((PooledConnectionAndInfo)obj).getPooledConnection();
 159  64
             pc.removeConnectionEventListener(this);
 160  64
             pcMap.remove(pc);
 161  64
             pc.close(); 
 162  
         }
 163  64
     }
 164  
 
 165  
     public boolean validateObject(Object obj) {
 166  0
         boolean valid = false;
 167  0
         if (obj instanceof PooledConnectionAndInfo) {
 168  0
             PooledConnection pconn =
 169  
                 ((PooledConnectionAndInfo) obj).getPooledConnection();
 170  0
             String query = _validationQuery;
 171  0
             if (null != query) {
 172  0
                 Connection conn = null;
 173  0
                 Statement stmt = null;
 174  0
                 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  0
                 validatingMap.put(pconn, null);
 180  
                 try {
 181  0
                     conn = pconn.getConnection();
 182  0
                     stmt = conn.createStatement();
 183  0
                     rset = stmt.executeQuery(query);
 184  0
                     if (rset.next()) {
 185  0
                         valid = true;
 186  
                     } else {
 187  0
                         valid = false;
 188  
                     }
 189  0
                     if (_rollbackAfterValidation) {
 190  0
                         conn.rollback();
 191  
                     }
 192  0
                 } catch (Exception e) {
 193  0
                     valid = false;
 194  
                 } finally {
 195  0
                     if (rset != null) {
 196  
                         try {
 197  0
                             rset.close();
 198  0
                         } catch (Throwable t) {
 199  
                             // ignore
 200  0
                         }
 201  
                     }
 202  0
                     if (stmt != null) {
 203  
                         try {
 204  0
                             stmt.close();
 205  0
                         } catch (Throwable t) {
 206  
                             // ignore
 207  0
                         }
 208  
                     }
 209  0
                     if (conn != null) {
 210  
                         try {
 211  0
                             conn.close();
 212  0
                         } catch (Throwable t) {
 213  
                             // ignore
 214  0
                         }
 215  
                     }
 216  0
                     validatingMap.remove(pconn);
 217  0
                 }
 218  0
             } else {
 219  0
                 valid = true;
 220  
             }
 221  0
         } else {
 222  0
             valid = false;
 223  
         }
 224  0
         return valid;
 225  
     }
 226  
 
 227  
     public void passivateObject(Object obj) {
 228  4349
     }
 229  
 
 230  
     public void activateObject(Object obj) {
 231  4353
     }
 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  4349
         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  4349
         if (!validatingMap.containsKey(pc)) {
 248  4349
             Object info = pcMap.get(pc);
 249  4349
             if (info == null) {
 250  0
                 throw new IllegalStateException(NO_KEY_MESSAGE);
 251  
             }
 252  
 
 253  
             try {
 254  4349
                 _pool.returnObject(info);
 255  0
             } catch (Exception e) {
 256  0
                 System.err.println("CLOSING DOWN CONNECTION AS IT COULD "
 257  
                         + "NOT BE RETURNED TO THE POOL");
 258  0
                 pc.removeConnectionEventListener(this);
 259  
                 try {
 260  0
                     destroyObject(info);
 261  0
                 } catch (Exception e2) {
 262  0
                     System.err.println("EXCEPTION WHILE DESTROYING OBJECT "
 263  
                             + info);
 264  0
                     e2.printStackTrace();
 265  0
                 }
 266  4349
             }
 267  
         }
 268  4349
     }
 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  2
         PooledConnection pc = (PooledConnection)event.getSource();
 276  2
         if (null != event.getSQLException()) {
 277  0
             System.err.println(
 278  
                     "CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR ("
 279  
                     + event.getSQLException() + ")");
 280  
         }
 281  2
         pc.removeConnectionEventListener(this);
 282  
 
 283  2
         Object info = pcMap.get(pc);
 284  2
         if (info == null) {
 285  0
             throw new IllegalStateException(NO_KEY_MESSAGE);
 286  
         }
 287  
         try {
 288  2
             _pool.invalidateObject(info);
 289  0
         } catch (Exception e) {
 290  0
             System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info);
 291  0
             e.printStackTrace();
 292  2
         }
 293  2
     }
 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  2
         Object info = pcMap.get(pc);
 307  2
         if (info == null) {
 308  0
             throw new IllegalStateException(NO_KEY_MESSAGE);
 309  
         }
 310  
         try {
 311  2
             _pool.invalidateObject(info);  // Destroy instance and update pool counters
 312  2
             _pool.close();  // Clear any other instances in this pool and kill others as they come back
 313  0
         } catch (Exception ex) {
 314  0
             throw (SQLException) new SQLException("Error invalidating connection").initCause(ex);
 315  2
         }   
 316  2
     }
 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  2
         _password = password;
 325  2
     }
 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  2
         synchronized (this) {
 333  2
             if (username == null || !username.equals(_username)) {
 334  0
                 return;
 335  
             }
 336  2
         }
 337  
         try {
 338  2
             _pool.close();
 339  0
         } catch (Exception ex) {
 340  0
             throw (SQLException) new SQLException("Error closing connection pool").initCause(ex);
 341  2
         } 
 342  2
     }
 343  
     
 344  
 }