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; 024 025import javax.management.InstanceAlreadyExistsException; 026import javax.management.InstanceNotFoundException; 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 036 * connection, returns itself to an {@link ObjectPool} when 037 * closed. 038 * 039 * @author Rodney Waldhoff 040 * @author Glenn L. Nielsen 041 * @author James House 042 * @version $Revision: 1572242 $ $Date: 2014-02-26 20:34:39 +0000 (Wed, 26 Feb 2014) $ 043 * @since 2.0 044 */ 045public class PoolableConnection extends DelegatingConnection<Connection> 046 implements PoolableConnectionMXBean { 047 048 private static MBeanServer MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer(); 049 050 /** The pool to which I should return. */ 051 private ObjectPool<PoolableConnection> _pool = null; 052 053 private final ObjectName _jmxName; 054 055 // Use a prepared statement for validation, retaining the last used SQL to 056 // check if the validation query has changed. 057 private PreparedStatement validationPreparedStatement = null; 058 private String lastValidationSql = null; 059 060 /** 061 * 062 * @param conn my underlying connection 063 * @param pool the pool to which I should return when closed 064 */ 065 public PoolableConnection(Connection conn, 066 ObjectPool<PoolableConnection> pool, ObjectName jmxName) { 067 super(conn); 068 _pool = pool; 069 _jmxName = jmxName; 070 071 if (jmxName != null) { 072 try { 073 MBEAN_SERVER.registerMBean(this, jmxName); 074 } catch (InstanceAlreadyExistsException | 075 MBeanRegistrationException | NotCompliantMBeanException e) { 076 // For now, simply skip registration 077 } 078 } 079 } 080 081 082 @Override 083 protected void passivate() throws SQLException { 084 super.passivate(); 085 setClosedInternal(true); 086 } 087 088 089 /** 090 * {@inheritDoc} 091 * <p> 092 * This method should not be used by a client to determine whether or not a 093 * connection should be return to the connection pool (by calling 094 * {@link #close()}). Clients should always attempt to return a connection 095 * to the pool once it is no longer required. 096 */ 097 @Override 098 public boolean isClosed() throws SQLException { 099 if (isClosedInternal()) { 100 return true; 101 } 102 103 if (getDelegateInternal().isClosed()) { 104 // Something has gone wrong. The underlying connection has been 105 // closed without the connection being returned to the pool. Return 106 // it now. 107 close(); 108 return true; 109 } 110 111 return false; 112 } 113 114 115 /** 116 * Returns me to my pool. 117 */ 118 @Override 119 public synchronized void close() throws SQLException { 120 if (isClosedInternal()) { 121 // already closed 122 return; 123 } 124 125 boolean isUnderlyingConectionClosed; 126 try { 127 isUnderlyingConectionClosed = getDelegateInternal().isClosed(); 128 } catch (SQLException e) { 129 try { 130 _pool.invalidateObject(this); 131 } catch(IllegalStateException ise) { 132 // pool is closed, so close the connection 133 passivate(); 134 getInnermostDelegate().close(); 135 } catch (Exception ie) { 136 // DO NOTHING the original exception will be rethrown 137 } 138 throw new SQLException("Cannot close connection (isClosed check failed)", e); 139 } 140 141 /* Can't set close before this code block since the connection needs to 142 * be open when validation runs. Can't set close after this code black 143 * since by then the connection will have been returned to the pool and 144 * may have been borrowed by another thread. Therefore, the close flag 145 * is set in passivate(). 146 */ 147 if (!isUnderlyingConectionClosed) { 148 // Normal close: underlying connection is still open, so we 149 // simply need to return this proxy to the pool 150 try { 151 _pool.returnObject(this); 152 } catch(IllegalStateException e) { 153 // pool is closed, so close the connection 154 passivate(); 155 getInnermostDelegate().close(); 156 } catch(SQLException e) { 157 throw e; 158 } catch(RuntimeException e) { 159 throw e; 160 } catch(Exception e) { 161 throw new SQLException("Cannot close connection (return to pool failed)", e); 162 } 163 } else { 164 // Abnormal close: underlying connection closed unexpectedly, so we 165 // must destroy this proxy 166 try { 167 _pool.invalidateObject(this); 168 } catch(IllegalStateException e) { 169 // pool is closed, so close the connection 170 passivate(); 171 getInnermostDelegate().close(); 172 } catch (Exception e) { 173 throw new SQLException("Cannot close connection (invalidating pooled object failed)", e); 174 } 175 } 176 } 177 178 /** 179 * Actually close my underlying {@link Connection}. 180 */ 181 @Override 182 public void reallyClose() throws SQLException { 183 if (_jmxName != null) { 184 try { 185 MBEAN_SERVER.unregisterMBean(_jmxName); 186 } catch (MBeanRegistrationException | InstanceNotFoundException e) { 187 // Ignore 188 } 189 } 190 191 192 if (validationPreparedStatement != null) { 193 try { 194 validationPreparedStatement.close(); 195 } catch (SQLException sqle) { 196 // Ignore 197 } 198 } 199 200 super.closeInternal(); 201 } 202 203 204 /** 205 * Expose the {@link #toString()} method via a bean getter so it can be read 206 * as a property via JMX. 207 */ 208 @Override 209 public String getToString() { 210 return toString(); 211 } 212 213 214 public void validate(String sql, int timeout) throws SQLException { 215 if (sql == null || sql.length() == 0) { 216 if (timeout < 0) { 217 timeout = 0; 218 } 219 if (!isValid(timeout)) { 220 throw new SQLException("isValid() returned false"); 221 } 222 return; 223 } 224 225 if (!sql.equals(lastValidationSql)) { 226 lastValidationSql = sql; 227 // Has to be the innermost delegate else the prepared statement will 228 // be closed when the pooled connection is passivated. 229 validationPreparedStatement = 230 getInnermostDelegateInternal().prepareStatement(sql); 231 } 232 233 if (timeout > 0) { 234 validationPreparedStatement.setQueryTimeout(timeout); 235 } 236 237 try (ResultSet rs = validationPreparedStatement.executeQuery()) { 238 if(!rs.next()) { 239 throw new SQLException("validationQuery didn't return a row"); 240 } 241 } catch (SQLException sqle) { 242 throw sqle; 243 } 244 } 245} 246