StatementFacade.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.tomcat.jdbc.pool;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Wrapper;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.interceptor.AbstractCreateStatementInterceptor;
import org.apache.tomcat.jdbc.pool.util.MessageFactory;

/**
 * Facade that creates statement and result set proxies.
 * <p>
 * The handles this facade hands out implement only the standard JDBC interfaces, as do the proxies of the interceptors
 * that wrap statements beneath it, so the driver's own statement and result set classes are never visible to the
 * application through a cast. Every result set a pooled statement hands back, and every result set that one of those
 * handles hands back in turn, reaches the application as one of these handles, whatever the method that returned it was
 * called: a cursor that arrives as a plain object - a REF cursor OUT-parameter read through
 * {@link java.sql.CallableStatement#getObject(int)}, or the nested cursor a {@link java.sql.ResultSet#getObject(int)}
 * column value hands out - is sealed to the borrow like the ones the standard result set methods return. The
 * <code>Wrapper</code> methods are delegated to the driver, and <code>unwrap</code> is the supported way to reach
 * driver-specific methods through every layer. An unwrapped driver object, however, is a driver object rather than a
 * pooled handle: retiring these handles at the end of the borrow does not retire what was already unwrapped, and only
 * tracking the statements with {@link org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer} (or the application
 * closing them) keeps the statements underneath from outliving the borrow - unless an interceptor recycles them for the
 * next borrow, as a statement cache does.
 * <p>
 * The rule that decides what is sealed is a type rule, and its boundary is the type. A value a handle hands back
 * that is not itself a result set is passed through as it came (what a statement handle reports as its connection
 * is the pool's own handle, not the driver's), and a cursor that rides inside such a value reaches the application
 * over that same unsealed path: the {@link java.sql.Array} read for an ARRAY column or an ARRAY OUT-parameter
 * hands out its cursors through {@link java.sql.Array#getResultSet()}, a {@link java.sql.Struct} through its
 * attributes, and the deprecated {@link java.sql.Ref} a <code>getRef</code> call hands back is itself the
 * driver's object; the {@link java.sql.Blob}, {@link java.sql.Clob} and {@link java.sql.SQLXML} locators are the
 * same shape without a cursor in them. None of that is sealed to the borrow, just as what <code>unwrap()</code>
 * hands back is not: this facade seals the result sets a pooled statement hands out and the result sets those
 * handles hand out in turn, not cursors that arrive inside values of another type - the REF cursor OUT-parameter
 * and the nested cursor named above are themselves result sets, which is why they are sealed and these composites
 * are not. Whether a driver object kept past the borrow still reads, and over what session, is the driver's affair;
 * no handle of this facade retires it.
 */
public class StatementFacade extends AbstractCreateStatementInterceptor {

    private static final Log logger = LogFactory.getLog(StatementFacade.class);

    /**
     * Whether the borrow this facade was created for has ended. The pool creates one facade per borrow, so this says
     * when every handle this instance handed out stopped belonging to a connection the application still holds.
     */
    private volatile boolean borrowEnded = false;

    /**
     * Constructs a StatementFacade.
     *
     * @param interceptor the next interceptor in the chain
     */
    protected StatementFacade(JdbcInterceptor interceptor) {
        setUseEquals(interceptor.isUseEquals());
        setNext(interceptor);
    }

    /**
     * Marks the borrow as ended, which closes every statement and result set handle this facade handed out. Called
     * before the connection is handed back, so a handle cannot outlive the borrow and end up driving the session of the
     * next borrower. A borrow that is abandoned instead never runs this method, but the pool destroys an abandoned
     * session rather than lending it again, so those handles only fail at the driver.
     */
    @Override
    public void closeInvoked() {
        borrowEnded = true;
    }

    /**
     * Creates a statement interceptor to monitor query response times
     */
    @Override
    public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
        try {
            String name = method.getName();
            Constructor<?> constructor = null;
            String sql = null;
            if (compare(CREATE_STATEMENT, name)) {
                // createStatement
                constructor = getConstructor(CREATE_STATEMENT_IDX, Statement.class);
            } else if (compare(PREPARE_STATEMENT, name)) {
                // prepareStatement
                constructor = getConstructor(PREPARE_STATEMENT_IDX, PreparedStatement.class);
                sql = (String) args[0];
            } else if (compare(PREPARE_CALL, name)) {
                // prepareCall
                constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class);
                sql = (String) args[0];
            } else {
                // Do nothing
                return statement;
            }
            return constructor.newInstance(new Object[] { new StatementProxy(statement, sql, proxy) });
        } catch (Exception x) {
            logger.warn("Unable to create statement proxy.", x);
        }
        return statement;
    }

    /**
     * Proxy for a SQL statement used to measure query execute time.
     */
    protected class StatementProxy implements InvocationHandler {
        /**
         * Indicates whether this statement has been closed.
         */
        protected boolean closed = false;
        /**
         * The delegated statement, nulled once a close has been forwarded. A handle can be closed from a thread other
         * than the one invoking on it, so the field is read by more threads than the one that nulls it and, like the
         * borrow-ended flag, must be volatile to reach them at all.
         */
        protected volatile Object delegate;
        /**
         * The SQL query text.
         */
        protected final String query;
        /**
         * The pooled connection the statement was created from, as reported by
         * {@link java.sql.Statement#getConnection()}. It is the handle the application borrowed, so it stops working
         * once that borrow has been returned, while the driver's own connection would have gone on serving whoever
         * borrowed the connection next.
         */
        protected final Object connection;

        /**
         * Constructs a StatementProxy.
         *
         * @param parent     the parent statement
         * @param query      the SQL query text
         * @param connection the pooled connection the statement was created from
         */
        public StatementProxy(Object parent, String query, Object connection) {
            this.delegate = parent;
            this.query = query;
            this.connection = connection;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (compare(TOSTRING_VAL, method)) {
                return toString();
            }
            if (compare(EQUALS_VAL, method)) {
                if (args[0] == null || !Proxy.isProxyClass(args[0].getClass())) {
                    return Boolean.FALSE;
                }
                return Boolean.valueOf(this.equals(Proxy.getInvocationHandler(args[0])));
            }
            if (compare(HASHCODE_VAL, method)) {
                return Integer.valueOf(this.hashCode());
            }
            // Read the state once: whether the borrow has ended must not
            // change while the call is being answered, and the flag and the
            // delegate are two separate fields, so a call that read the
            // delegate as live can find it nulled by the next read - a
            // close from another thread, interrupting a statement this call
            // is blocking on, nulls it. A close that landed in between is
            // then answered as closed, on the missing delegate as much as
            // on the flag, rather than by invoking on a null object: that
            // is a NullPointerException, and the application is owed the
            // SQLException the closed contract promises. The window this
            // cannot close is the race itself: a call that reads the
            // delegate while it is still live goes to it, an outcome
            // indistinguishable from having simply outrun the close.
            boolean ended = borrowEnded;
            Object target = delegate;
            if (compare(CLOSE_VAL, method)) {
                if (target == null || ended) {
                    return null;
                }
            }
            if (compare(ISCLOSED_VAL, method)) {
                if (target == null || ended) {
                    return Boolean.TRUE;
                }
            }
            if (target == null) {
                throw new SQLException(MessageFactory.get("statementFacade.statementClosed"));
            }
            if (ended) {
                // The delegate is still open, but it is open on a session
                // that belongs to the next borrower now.
                throw new SQLException(MessageFactory.get("statementFacade.statementClosedConnectionReturned"));
            }
            // The connection a statement reports is the pooled handle the
            // application created it from, not the driver's own connection.
            // Forwarding the call instead hands the application a handle on
            // the physical session: it survives the borrow it came from and
            // bypasses every interceptor, so statements made through it
            // escape the close-on-return tracking and any statement cache.
            if (compare(GETCONNECTION_VAL, method)) {
                return connection;
            }

            Object result;
            try {
                // Invoke next, on the delegate this call read as live: the
                // snapshot keeps a concurrent close from replacing the
                // receiver with null between the guard and the call
                result = method.invoke(target, args);

                // Seal every result set the call hands back, whatever the
                // method that returned it was called. Matching the three
                // names the standard API gives them (executeQuery,
                // getResultSet, getGeneratedKeys) misses the result sets
                // declared to come back as an Object: a cursor OUT-parameter
                // reaches the application through
                // CallableStatement.getObject(...). The return type sees them
                // all, and a null result simply is no result set. The Wrapper
                // methods are kept out of this: unwrap() is the documented
                // way to reach the driver's own object, so what it hands back
                // stays the driver's object even when that object implements
                // ResultSet. ResultSetProxy mirrors this rule for the result
                // sets a result set itself hands out.
                if (result instanceof ResultSet && method.getDeclaringClass() != Wrapper.class) {
                    return getConstructor(RESULTSET_IDX, ResultSet.class)
                            .newInstance(new ResultSetProxy(result, proxy));
                }
            } catch (Throwable t) {
                if (t instanceof InvocationTargetException && t.getCause() != null) {
                    throw t.getCause();
                } else {
                    throw t;
                }
            }
            // Perform close cleanup
            if (compare(CLOSE_VAL, method)) {
                delegate = null;
            }
            return result;
        }

        @Override
        public int hashCode() {
            return System.identityHashCode(this);
        }

        @Override
        public boolean equals(Object obj) {
            return this == obj;
        }

        @Override
        public String toString() {
            // Reads the live delegate rather than a snapshot: a description
            // says what the proxy holds as it is asked.
            StringBuffer buf = new StringBuffer(StatementProxy.class.getName());
            buf.append("[Proxy=");
            buf.append(hashCode());
            buf.append("; Query=");
            buf.append(query);
            buf.append("; Delegate=");
            buf.append(delegate);
            buf.append(']');
            return buf.toString();
        }
    }

    /**
     * Proxy for a ResultSet.
     */
    protected class ResultSetProxy implements InvocationHandler {

        private final Object parent;
        /**
         * The delegated result set, nulled once a close has been forwarded. Read by other threads than the one that
         * nulls it, so volatile, as in the statement proxy.
         */
        private volatile Object delegate;

        /**
         * Constructs a ResultSetProxy.
         *
         * @param delegate the ResultSet to delegate to
         * @param parent   the statement handle the result set was read from, which
         *                     {@link java.sql.ResultSet#getStatement()} answers; a result set a handle hands out as a
         *                     column value is parented to the same statement
         */
        public ResultSetProxy(Object delegate, Object parent) {
            this.delegate = delegate;
            this.parent = parent;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (compare(TOSTRING_VAL, method)) {
                return toString();
            }
            if (compare(EQUALS_VAL, method)) {
                if (args[0] == null || !Proxy.isProxyClass(args[0].getClass())) {
                    return Boolean.FALSE;
                }
                return Boolean.valueOf(this.equals(Proxy.getInvocationHandler(args[0])));
            }
            if (compare(HASHCODE_VAL, method)) {
                return Integer.valueOf(this.hashCode());
            }
            // Read the state once, both the borrow-ended flag and the
            // delegate, for the statement proxy's reason: a close from
            // another thread nulls the delegate, and a call that read the
            // delegate as live must not find it nulled at the forward,
            // where Method.invoke on a null receiver would answer with a
            // NullPointerException rather than the SQLException the closed
            // contract promises.
            boolean ended = borrowEnded;
            Object target = delegate;
            if (compare(CLOSE_VAL, method)) {
                if (target == null || ended) {
                    return null;
                }
            }
            if (compare(ISCLOSED_VAL, method)) {
                if (target == null || ended) {
                    return Boolean.TRUE;
                }
            }
            if (target == null) {
                throw new SQLException(MessageFactory.get("statementFacade.resultSetClosed"));
            }
            if (ended) {
                // The cursor is still open, but it is open on a session that
                // belongs to the next borrower now.
                throw new SQLException(MessageFactory.get("statementFacade.resultSetClosedConnectionReturned"));
            }

            if (compare(GET_STATEMENT, method)) {
                return parent;
            }

            Object result;
            try {
                // Invoke next, on the delegate this call read as live, as
                // in the statement proxy
                result = method.invoke(target, args);

                // The statement proxy's rule, mirrored one layer down: a
                // cursor-valued column - a nested cursor, or a REF cursor
                // selected as a column - arrives through a getObject(...)
                // overload, which comes back as an Object, and would
                // otherwise reach the application with nothing tying it to
                // this borrow. The new handle is parented to the statement,
                // as the statement proxy parents its result sets, so that
                // getStatement() keeps answering a Statement; the Wrapper
                // carve-out is the statement proxy's, for its reason there.
                if (result instanceof ResultSet && method.getDeclaringClass() != Wrapper.class) {
                    return getConstructor(RESULTSET_IDX, ResultSet.class)
                            .newInstance(new ResultSetProxy(result, parent));
                }
            } catch (Throwable t) {
                if (t instanceof InvocationTargetException && t.getCause() != null) {
                    throw t.getCause();
                } else {
                    throw t;
                }
            }
            // Perform close cleanup
            if (compare(CLOSE_VAL, method)) {
                delegate = null;
            }
            return result;
        }

        @Override
        public int hashCode() {
            return System.identityHashCode(this);
        }

        @Override
        public boolean equals(Object obj) {
            return this == obj;
        }

        @Override
        public String toString() {
            // Reads the live delegate rather than a snapshot, as in the
            // statement proxy.
            return ResultSetProxy.class.getName() + "[Proxy=" + hashCode() + "; Delegate=" + delegate + ']';
        }
    }

}