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