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