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