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