001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.dbcp2; 018 019import java.lang.management.ManagementFactory; 020import java.sql.Connection; 021import java.sql.PreparedStatement; 022import java.sql.ResultSet; 023import java.sql.SQLException; 024import java.util.Collection; 025 026import javax.management.InstanceAlreadyExistsException; 027import javax.management.InstanceNotFoundException; 028import javax.management.MBeanRegistrationException; 029import javax.management.MBeanServer; 030import javax.management.NotCompliantMBeanException; 031import javax.management.ObjectName; 032 033import org.apache.commons.pool2.ObjectPool; 034 035/** 036 * A delegating connection that, rather than closing the underlying 037 * connection, returns itself to an {@link ObjectPool} when 038 * closed. 039 * 040 * @author Rodney Waldhoff 041 * @author Glenn L. Nielsen 042 * @author James House 043 * @version $Id: PoolableConnection.java 1658644 2015-02-10 08:59:07Z tn $ 044 * @since 2.0 045 */ 046public class PoolableConnection extends DelegatingConnection<Connection> 047 implements PoolableConnectionMXBean { 048 049 private static MBeanServer MBEAN_SERVER = null; 050 051 static { 052 try { 053 MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer(); 054 } catch (Exception ex) { 055 // ignore - JMX not available 056 } 057 } 058 059 /** The pool to which I should return. */ 060 private final ObjectPool<PoolableConnection> _pool; 061 062 private final ObjectName _jmxName; 063 064 // Use a prepared statement for validation, retaining the last used SQL to 065 // check if the validation query has changed. 066 private PreparedStatement validationPreparedStatement = null; 067 private String lastValidationSql = null; 068 069 /** 070 * Indicate that unrecoverable SQLException was thrown when using this connection. 071 * Such a connection should be considered broken and not pass validation in the future. 072 */ 073 private boolean _fatalSqlExceptionThrown = false; 074 075 /** 076 * SQL_STATE codes considered to signal fatal conditions. Overrides the 077 * defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting 078 * with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). 079 */ 080 private final Collection<String> _disconnectionSqlCodes; 081 082 /** Whether or not to fast fail validation after fatal connection errors */ 083 private final boolean _fastFailValidation; 084 085 /** 086 * 087 * @param conn my underlying connection 088 * @param pool the pool to which I should return when closed 089 * @param jmxName JMX name 090 * @param disconnectSqlCodes SQL_STATE codes considered fatal disconnection errors 091 * @param fastFailValidation true means fatal disconnection errors cause subsequent 092 * validations to fail immediately (no attempt to run query or isValid) 093 */ 094 public PoolableConnection(Connection conn, 095 ObjectPool<PoolableConnection> pool, ObjectName jmxName, Collection<String> disconnectSqlCodes, 096 boolean fastFailValidation) { 097 super(conn); 098 _pool = pool; 099 _jmxName = jmxName; 100 _disconnectionSqlCodes = disconnectSqlCodes; 101 _fastFailValidation = fastFailValidation; 102 103 if (jmxName != null) { 104 try { 105 MBEAN_SERVER.registerMBean(this, jmxName); 106 } catch (InstanceAlreadyExistsException | 107 MBeanRegistrationException | NotCompliantMBeanException e) { 108 // For now, simply skip registration 109 } 110 } 111 } 112 113 /** 114 * 115 * @param conn my underlying connection 116 * @param pool the pool to which I should return when closed 117 * @param jmxName JMX name 118 */ 119 public PoolableConnection(Connection conn, 120 ObjectPool<PoolableConnection> pool, ObjectName jmxName) { 121 this(conn, pool, jmxName, null, false); 122 } 123 124 125 @Override 126 protected void passivate() throws SQLException { 127 super.passivate(); 128 setClosedInternal(true); 129 } 130 131 132 /** 133 * {@inheritDoc} 134 * <p> 135 * This method should not be used by a client to determine whether or not a 136 * connection should be return to the connection pool (by calling 137 * {@link #close()}). Clients should always attempt to return a connection 138 * to the pool once it is no longer required. 139 */ 140 @Override 141 public boolean isClosed() throws SQLException { 142 if (isClosedInternal()) { 143 return true; 144 } 145 146 if (getDelegateInternal().isClosed()) { 147 // Something has gone wrong. The underlying connection has been 148 // closed without the connection being returned to the pool. Return 149 // it now. 150 close(); 151 return true; 152 } 153 154 return false; 155 } 156 157 158 /** 159 * Returns me to my pool. 160 */ 161 @Override 162 public synchronized void close() throws SQLException { 163 if (isClosedInternal()) { 164 // already closed 165 return; 166 } 167 168 boolean isUnderlyingConectionClosed; 169 try { 170 isUnderlyingConectionClosed = getDelegateInternal().isClosed(); 171 } catch (SQLException e) { 172 try { 173 _pool.invalidateObject(this); 174 } catch(IllegalStateException ise) { 175 // pool is closed, so close the connection 176 passivate(); 177 getInnermostDelegate().close(); 178 } catch (Exception ie) { 179 // DO NOTHING the original exception will be rethrown 180 } 181 throw new SQLException("Cannot close connection (isClosed check failed)", e); 182 } 183 184 /* Can't set close before this code block since the connection needs to 185 * be open when validation runs. Can't set close after this code block 186 * since by then the connection will have been returned to the pool and 187 * may have been borrowed by another thread. Therefore, the close flag 188 * is set in passivate(). 189 */ 190 if (isUnderlyingConectionClosed) { 191 // Abnormal close: underlying connection closed unexpectedly, so we 192 // must destroy this proxy 193 try { 194 _pool.invalidateObject(this); 195 } catch(IllegalStateException e) { 196 // pool is closed, so close the connection 197 passivate(); 198 getInnermostDelegate().close(); 199 } catch (Exception e) { 200 throw new SQLException("Cannot close connection (invalidating pooled object failed)", e); 201 } 202 } else { 203 // Normal close: underlying connection is still open, so we 204 // simply need to return this proxy to the pool 205 try { 206 _pool.returnObject(this); 207 } catch(IllegalStateException e) { 208 // pool is closed, so close the connection 209 passivate(); 210 getInnermostDelegate().close(); 211 } catch(SQLException e) { 212 throw e; 213 } catch(RuntimeException e) { 214 throw e; 215 } catch(Exception e) { 216 throw new SQLException("Cannot close connection (return to pool failed)", e); 217 } 218 } 219 } 220 221 /** 222 * Actually close my underlying {@link Connection}. 223 */ 224 @Override 225 public void reallyClose() throws SQLException { 226 if (_jmxName != null) { 227 try { 228 MBEAN_SERVER.unregisterMBean(_jmxName); 229 } catch (MBeanRegistrationException | InstanceNotFoundException e) { 230 // Ignore 231 } 232 } 233 234 235 if (validationPreparedStatement != null) { 236 try { 237 validationPreparedStatement.close(); 238 } catch (SQLException sqle) { 239 // Ignore 240 } 241 } 242 243 super.closeInternal(); 244 } 245 246 247 /** 248 * Expose the {@link #toString()} method via a bean getter so it can be read 249 * as a property via JMX. 250 */ 251 @Override 252 public String getToString() { 253 return toString(); 254 } 255 256 /** 257 * Validates the connection, using the following algorithm: 258 * <ol> 259 * <li>If {@code fastFailValidation} (constructor argument) is {@code true} and 260 * this connection has previously thrown a fatal disconnection exception, 261 * a {@code SQLException} is thrown. </li> 262 * <li>If {@code sql} is null, the driver's 263 * #{@link Connection#isValid(int) isValid(timeout)} is called. 264 * If it returns {@code false}, {@code SQLException} is thrown; 265 * otherwise, this method returns successfully.</li> 266 * <li>If {@code sql} is not null, it is executed as a query and if the resulting 267 * {@code ResultSet} contains at least one row, this method returns 268 * successfully. If not, {@code SQLException} is thrown.</li> 269 * </ol> 270 * @param sql validation query 271 * @param timeout validation timeout 272 * @throws SQLException if validation fails or an SQLException occurs during validation 273 */ 274 public void validate(String sql, int timeout) throws SQLException { 275 if (_fastFailValidation && _fatalSqlExceptionThrown) { 276 throw new SQLException(Utils.getMessage("poolableConnection.validate.fastFail")); 277 } 278 279 if (sql == null || sql.length() == 0) { 280 if (timeout < 0) { 281 timeout = 0; 282 } 283 if (!isValid(timeout)) { 284 throw new SQLException("isValid() returned false"); 285 } 286 return; 287 } 288 289 if (!sql.equals(lastValidationSql)) { 290 lastValidationSql = sql; 291 // Has to be the innermost delegate else the prepared statement will 292 // be closed when the pooled connection is passivated. 293 validationPreparedStatement = 294 getInnermostDelegateInternal().prepareStatement(sql); 295 } 296 297 if (timeout > 0) { 298 validationPreparedStatement.setQueryTimeout(timeout); 299 } 300 301 try (ResultSet rs = validationPreparedStatement.executeQuery()) { 302 if(!rs.next()) { 303 throw new SQLException("validationQuery didn't return a row"); 304 } 305 } catch (SQLException sqle) { 306 throw sqle; 307 } 308 } 309 310 /** 311 * Checks the SQLState of the input exception and any nested SQLExceptions it wraps. 312 * <p> 313 * If {@link #getDisconnectSqlCodes() disconnectSQLCodes} has been set, sql states 314 * are compared to those in the configured list of fatal exception codes. If this 315 * property is not set, codes are compared against the default codes in 316 * #{@link Utils.DISCONNECTION_SQL_CODES} and in this case anything starting with 317 * #{link Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.</p> 318 * 319 * @param e SQLException to be examined 320 * @return true if the exception signals a disconnection 321 */ 322 private boolean isDisconnectionSqlException(SQLException e) { 323 boolean fatalException = false; 324 String sqlState = e.getSQLState(); 325 if (sqlState != null) { 326 fatalException = _disconnectionSqlCodes == null ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) 327 || Utils.DISCONNECTION_SQL_CODES.contains(sqlState) : _disconnectionSqlCodes.contains(sqlState); 328 if (!fatalException) { 329 if (e.getNextException() != null) { 330 fatalException = isDisconnectionSqlException(e.getNextException()); 331 } 332 } 333 } 334 return fatalException; 335 } 336 337 @Override 338 protected void handleException(SQLException e) throws SQLException { 339 _fatalSqlExceptionThrown |= isDisconnectionSqlException(e); 340 super.handleException(e); 341 } 342} 343