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