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