Coverage Report - org.apache.commons.dbcp.datasources.KeyedCPDSConnectionFactory
 
Classes in this File Line Coverage Branch Coverage Complexity
KeyedCPDSConnectionFactory
46%
54/117
30%
9/30
3.308
 
 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  80
     private final Map /* <PooledConnection, null> */ validatingMap = new HashMap();
 60  
     
 61  
     /**
 62  
      * Map of PooledConnectionAndInfo instances
 63  
      */
 64  80
     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  0
         this(cpds , pool, validationQuery, false);  
 77  0
     }
 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  80
                                       boolean rollbackAfterValidation) {
 94  80
         _cpds = cpds;
 95  80
         _pool = pool;
 96  80
         pool.setFactory(this);
 97  80
         _validationQuery = validationQuery;
 98  80
         _rollbackAfterValidation = rollbackAfterValidation;
 99  80
     }
 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  2
         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  316
         Object obj = null;
 119  316
         UserPassKey upkey = (UserPassKey)key;
 120  
 
 121  316
         PooledConnection pc = null;
 122  316
         String username = upkey.getUsername();
 123  316
         String password = upkey.getPassword();
 124  316
         if (username == null) {
 125  80
             pc = _cpds.getPooledConnection();
 126  
         } else {
 127  236
             pc = _cpds.getPooledConnection(username, password);
 128  
         }
 129  
 
 130  312
         if (pc == null) {
 131  0
             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  312
         pc.addConnectionEventListener(this);
 137  312
         obj = new PooledConnectionAndInfo(pc, username, password);
 138  312
         pcMap.put(pc, obj);
 139  
 
 140  312
         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  66
         if (obj instanceof PooledConnectionAndInfo) {
 148  66
             PooledConnection pc = ((PooledConnectionAndInfo)obj).getPooledConnection();
 149  66
             pc.removeConnectionEventListener(this);
 150  66
             pcMap.remove(pc);
 151  66
             pc.close(); 
 152  
         }
 153  66
     }
 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  0
         boolean valid = false;
 164  0
         if (obj instanceof PooledConnectionAndInfo) {
 165  0
             PooledConnection pconn =
 166  
                 ((PooledConnectionAndInfo)obj).getPooledConnection();
 167  0
             String query = _validationQuery;
 168  0
             if (null != query) {
 169  0
                 Connection conn = null;
 170  0
                 Statement stmt = null;
 171  0
                 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  0
                 validatingMap.put(pconn, null);
 177  
                 try {
 178  0
                     conn = pconn.getConnection();
 179  0
                     stmt = conn.createStatement();
 180  0
                     rset = stmt.executeQuery(query);
 181  0
                     if (rset.next()) {
 182  0
                         valid = true;
 183  
                     } else {
 184  0
                         valid = false;
 185  
                     }
 186  0
                     if (_rollbackAfterValidation) {
 187  0
                         conn.rollback();
 188  
                     }
 189  0
                 } catch(Exception e) {
 190  0
                     valid = false;
 191  
                 } finally {
 192  0
                     if (rset != null) {
 193  
                         try {
 194  0
                             rset.close();
 195  0
                         } catch (Throwable t) {
 196  
                             // ignore
 197  0
                         }
 198  
                     }
 199  0
                     if (stmt != null) {
 200  
                         try {
 201  0
                             stmt.close();
 202  0
                         } catch (Throwable t) {
 203  
                             // ignore
 204  0
                         }
 205  
                     }
 206  0
                     if (conn != null) {
 207  
                         try {
 208  0
                             conn.close();
 209  0
                         } catch (Throwable t) {
 210  
                             // ignore
 211  0
                         }
 212  
                     }
 213  0
                     validatingMap.remove(pconn);
 214  0
                 }
 215  0
             } else {
 216  0
                 valid = true;
 217  
             }
 218  0
         } else {
 219  0
             valid = false;
 220  
         }
 221  0
         return valid;
 222  
     }
 223  
 
 224  
     public void passivateObject(Object key, Object obj) {
 225  3326
     }
 226  
 
 227  
     public void activateObject(Object key, Object obj) {
 228  3332
     }
 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  3326
         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  3326
         if (!validatingMap.containsKey(pc)) {
 246  3326
             PooledConnectionAndInfo info =
 247  
                 (PooledConnectionAndInfo) pcMap.get(pc);
 248  3326
             if (info == null) {
 249  0
                 throw new IllegalStateException(NO_KEY_MESSAGE);
 250  
             }
 251  
             try {
 252  3326
                 _pool.returnObject(info.getUserPassKey(), info);
 253  0
             } catch (Exception e) {
 254  0
                 System.err.println("CLOSING DOWN CONNECTION AS IT COULD " +
 255  
                 "NOT BE RETURNED TO THE POOL");
 256  0
                 pc.removeConnectionEventListener(this);
 257  
                 try {
 258  0
                     _pool.invalidateObject(info.getUserPassKey(), info);
 259  0
                 } catch (Exception e3) {
 260  0
                     System.err.println("EXCEPTION WHILE DESTROYING OBJECT " +
 261  
                             info);
 262  0
                     e3.printStackTrace();
 263  0
                 }
 264  3326
             }
 265  
         }
 266  3326
     }
 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  2
         PooledConnection pc = (PooledConnection)event.getSource();
 274  2
         if (null != event.getSQLException()) {
 275  0
             System.err
 276  
                 .println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" +
 277  
                          event.getSQLException() + ")");
 278  
         }
 279  2
         pc.removeConnectionEventListener(this);
 280  
 
 281  2
         PooledConnectionAndInfo info = (PooledConnectionAndInfo) pcMap.get(pc);
 282  2
         if (info == null) {
 283  0
             throw new IllegalStateException(NO_KEY_MESSAGE);
 284  
         }
 285  
         try {
 286  2
             _pool.invalidateObject(info.getUserPassKey(), info);
 287  0
         } catch (Exception e) {
 288  0
             System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info);
 289  0
             e.printStackTrace();
 290  2
         }
 291  2
     }
 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  4
         PooledConnectionAndInfo info = (PooledConnectionAndInfo) pcMap.get(pc);
 306  4
         if (info == null) {
 307  0
             throw new IllegalStateException(NO_KEY_MESSAGE);
 308  
         }
 309  4
         UserPassKey key = info.getUserPassKey();
 310  
         try {
 311  4
             _pool.invalidateObject(key, info);  // Destroy and update pool counters
 312  4
             _pool.clear(key); // Remove any idle instances with this key
 313  0
         } catch (Exception ex) {
 314  0
             throw (SQLException) new SQLException("Error invalidating connection").initCause(ex);
 315  4
         }
 316  4
     }
 317  
     
 318  
     /**
 319  
      * Does nothing.  This factory does not cache user credentials.
 320  
      */
 321  
     public void setPassword(String password) {
 322  4
     }
 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  0
             _pool.clear(new UserPassKey(username, null));
 332  0
         } catch (Exception ex) {
 333  0
             throw (SQLException) new SQLException("Error closing connection pool").initCause(ex);
 334  0
         } 
 335  0
     }
 336  
     
 337  
 }