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 * @since 2.0 039 */ 040public class PoolableConnection extends DelegatingConnection<Connection> implements PoolableConnectionMXBean { 041 042 private static MBeanServer MBEAN_SERVER; 043 044 static { 045 try { 046 MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer(); 047 } catch (NoClassDefFoundError | Exception ex) { 048 // ignore - JMX not available 049 } 050 } 051 052 /** The pool to which I should return. */ 053 private final ObjectPool<PoolableConnection> pool; 054 055 private final ObjectNameWrapper jmxObjectName; 056 057 // Use a prepared statement for validation, retaining the last used SQL to 058 // check if the validation query has changed. 059 private PreparedStatement validationPreparedStatement; 060 private String lastValidationSql; 061 062 /** 063 * Indicate that unrecoverable SQLException was thrown when using this connection. Such a connection should be 064 * considered broken and not pass validation in the future. 065 */ 066 private boolean fatalSqlExceptionThrown = false; 067 068 /** 069 * SQL_STATE codes considered to signal fatal conditions. Overrides the defaults in 070 * {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). 071 */ 072 private final Collection<String> disconnectionSqlCodes; 073 074 /** Whether or not to fast fail validation after fatal connection errors */ 075 private final boolean fastFailValidation; 076 077 /** 078 * 079 * @param conn 080 * my underlying connection 081 * @param pool 082 * the pool to which I should return when closed 083 * @param jmxObjectName 084 * JMX name 085 * @param disconnectSqlCodes 086 * SQL_STATE codes considered fatal disconnection errors 087 * @param fastFailValidation 088 * true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to 089 * run query or isValid) 090 */ 091 public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool, 092 final ObjectName jmxObjectName, final Collection<String> disconnectSqlCodes, 093 final boolean fastFailValidation) { 094 super(conn); 095 this.pool = pool; 096 this.jmxObjectName = ObjectNameWrapper.wrap(jmxObjectName); 097 this.disconnectionSqlCodes = disconnectSqlCodes; 098 this.fastFailValidation = fastFailValidation; 099 100 if (jmxObjectName != null) { 101 try { 102 MBEAN_SERVER.registerMBean(this, jmxObjectName); 103 } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { 104 // For now, simply skip registration 105 } 106 } 107 } 108 109 /** 110 * 111 * @param conn 112 * my underlying connection 113 * @param pool 114 * the pool to which I should return when closed 115 * @param jmxName 116 * JMX name 117 */ 118 public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool, 119 final ObjectName jmxName) { 120 this(conn, pool, jmxName, null, true); 121 } 122 123 @Override 124 protected void passivate() throws SQLException { 125 super.passivate(); 126 setClosedInternal(true); 127 if (getDelegateInternal() instanceof PoolingConnection) { 128 ((PoolingConnection) getDelegateInternal()).connectionReturnedToPool(); 129 } 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 * The validation SQL query. 259 * @param timeoutSeconds 260 * The validation timeout in seconds. 261 * @throws SQLException 262 * Thrown when validation fails or an SQLException occurs during validation 263 */ 264 public void validate(final String sql, int timeoutSeconds) 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 (timeoutSeconds < 0) { 271 timeoutSeconds = 0; 272 } 273 if (!isValid(timeoutSeconds)) { 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 (timeoutSeconds > 0) { 287 validationPreparedStatement.setQueryTimeout(timeoutSeconds); 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 #disconnectionSqlCodes} 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 336 /** 337 * @return The disconnection SQL codes. 338 * @since 2.6.0 339 */ 340 public Collection<String> getDisconnectionSqlCodes() { 341 return disconnectionSqlCodes; 342 } 343 344 /** 345 * @return Whether to fail-fast. 346 * @since 2.6.0 347 */ 348 public boolean isFastFailValidation() { 349 return fastFailValidation; 350 } 351}