Package org.ow2.easybeans.component.jdbcpool

Source Code of org.ow2.easybeans.component.jdbcpool.JManagedConnection

/**
* EasyBeans
* Copyright (C) 2006-2007 Bull S.A.S.
* Contact: easybeans@ow2.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
* USA
*
* --------------------------------------------------------------------------
* $Id: JManagedConnection.java 5369 2010-02-24 14:58:19Z benoitf $
* --------------------------------------------------------------------------
*/

package org.ow2.easybeans.component.jdbcpool;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.transaction.Transaction;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

/**
* This class represents the connection managed by the pool. This connection is
* a managed connection and is notified of the transaction events.
* @author Philippe Durieux
* @author Florent Benoit
*/
public class JManagedConnection extends AbsProxy {

    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(JManagedConnection.class);

    /**
     * Connection to the database.
     */
    private Connection physicalConnection = null;

    /**
     * Connection returned to the user.
     */
    private IConnection implConn = null;

    /**
     * Maximum of prepared statements.
     */
    private int pstmtmax = 0;

    /**
     * Current number of opened prepared statements.
     */
    private int psOpenNb = 0;

    /**
     * Event listeners (of PooledConnection).
     */
    private Vector<ConnectionEventListener> eventListeners = new Vector<ConnectionEventListener>();

    /**
     * count of opening this connection. >0 if open.
     */
    private int open = 0;

    /**
     * Transaction timeout value.
     */
    private int timeout = 0;

    /**
     * Transaction the connection is involved with.
     */
    private Transaction tx = null;

    /**
     * Counter of all managed connections created.
     */
    private static int objcount = 0;

    /**
     * Identifier of this connection.
     */
    private final int identifier;

    /**
     * Prepared statements that were reused.
     */
    private int reUsedPreparedStatements = 0;

    /**
     * List of PreparedStatement in the pool.
     */
    private Map<String, IPreparedStatement> psList = null;

    /**
     * Link to the connection manager.
     */
    private ConnectionManager ds = null;

    /**
     * Time of the death for this connection.
     */
    private long deathTime = 0;

    /**
     * Time for closing this connection.
     */
    private long closeTime = 0;

    /**
     * Builds a new managed connection on a JDBC connection.
     * @param physicalConnection the physical JDBC Connection.
     * @param ds the connection manager
     */
    public JManagedConnection(final Connection physicalConnection, final ConnectionManager ds) {
        this.physicalConnection = physicalConnection;
        this.ds = ds;

        // make a proxy on our object.
        IManagedConnection managedConnectionProxy = (IManagedConnection) Proxy.newProxyInstance(IManagedConnection.class
                .getClassLoader(), new Class[] {IManagedConnection.class}, this);

        // An XAConnection holds 2 objects: 1 Connection + 1 XAResource
        this.implConn = (IConnection) Proxy.newProxyInstance(IConnection.class.getClassLoader(), new Class[] {IConnection.class},
                new JConnection(managedConnectionProxy, physicalConnection));

        open = 0;
        deathTime = System.currentTimeMillis() + ds.getMaxAgeMilli();

        identifier = objcount++;

        // Prepared statement.
        pstmtmax = ds.getPstmtMax();
        psOpenNb = 0;
        psList = Collections.synchronizedMap(new HashMap<String, IPreparedStatement>());

    }



    /**
     * Processes a method invocation on a proxy instance and returns the result.
     * This method will be invoked on an invocation handler when a method is
     * invoked on a proxy instance that it is associated with.
     * @param proxy the proxy instance that the method was invoked on
     * @param method the <code>Method</code> instance corresponding to the
     *        interface method invoked on the proxy instance.
     * @param args an array of objects containing the values of the arguments
     *        passed in the method invocation on the proxy instance, or
     *        <code>null</code> if interface method takes no arguments.
     * @return the value to return from the method invocation on the proxy
     *         instance.
     * @throws Throwable the exception to throw from the method invocation on
     *         the proxy instance. The exception's type must be assignable
     *         either to any of the exception types declared in the
     *         <code>throws</code> clause of the interface method or to the
     *         unchecked exception types <code>java.lang.RuntimeException</code>
     *         or <code>java.lang.Error</code>. If a checked exception is
     *         thrown by this method that is not assignable to any of the
     *         exception types declared in the <code>throws</code> clause of
     *         the interface method, then an UndeclaredThrowableException
     *         containing the exception that was thrown by this method will be
     *         thrown by the method invocation on the proxy instance.
     */
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {

        // Methods on the Object.class are not send on the connection impl
        if (method.getDeclaringClass().getName().equals("java.lang.Object")) {
            return handleObjectMethods(method, args);
        }

        String methodName = method.getName();

        // Methods that are part of the IManagedConnection interface
        if ("getIdentifier".equals(methodName)) {
            // return the identifier
            return Integer.valueOf(getIdentifier());
        } else if ("getXAResource".equals(methodName)) {
            // the XAResource is the proxy
            return proxy;
        } else if ("commit".equals(methodName)) {
            // commit
            commit((Xid) args[0], ((Boolean) args[1]).booleanValue(), (IManagedConnection) proxy);
            return null;
        } else if ("end".equals(methodName)) {
            // end
            end((Xid) args[0], ((Integer) args[1]).intValue());
            return null;
        } else if ("forget".equals(methodName)) {
            // forget
            forget((Xid) args[0]);
            return null;
        } else if ("prepare".equals(methodName)) {
            // prepare
            return Integer.valueOf(prepare((Xid) args[0]));
        } else if ("getTransactionTimeout".equals(methodName)) {
            return Integer.valueOf(getTransactionTimeout());
        } else if ("isSameRM".equals(methodName)) {
            // Compare object
            return Boolean.valueOf(isSameRM(args[0]));
        } else if ("recover".equals(methodName)) {
            // recover
            return recover(((Integer) args[0]).intValue());
        } else if ("rollback".equals(methodName)) {
            // forget
            rollback((Xid) args[0], (IManagedConnectionproxy);
            return null;
        } else if ("setTransactionTimeout".equals(methodName)) {
            // setTransactionTimeout
            setTransactionTimeout(((Integer) args[0]).intValue());
            return null;
        } else if ("start".equals(methodName)) {
            // start
            start((Xid) args[0], ((Integer) args[1]).intValue());
            return null;
        } else if ("getXAResource".equals(methodName)) {
            // return the proxy object as it's the XA Resource
            return proxy;
        } else if ("notifyClose".equals(methodName)) {
            notifyClose((IManagedConnection) proxy);
            return null;
        } else if ("notifyError".equals(methodName)) {
            notifyError((IManagedConnection) proxy, (SQLException) args[0]);
            return null;
        } else if ("compareTo".equals(methodName)) {
            return Integer.valueOf(compareTo((IManagedConnection) proxy, (IManagedConnection) args[0]));
        } else if ("getReUsedPreparedStatements".equals(methodName)) {
            return Integer.valueOf(getReUsedPreparedStatements());
        } else if ("setPstmtMax".equals(methodName)) {
            setPstmtMax(((Integer) args[0]).intValue());
            return null;
        } else if ("getConnection".equals(methodName)) {
            return getConnection();
        } else if ("close".equals(methodName)) {
            close();
            return null;
        } else if ("addConnectionEventListener".equals(methodName)) {
            addConnectionEventListener((ConnectionEventListener) args[0]);
            return null;
        } else if ("removeConnectionEventListener".equals(methodName)) {
            removeConnectionEventListener((ConnectionEventListener) args[0]);
            return null;
        } else if ("beforeCompletion".equals(methodName)) {
            beforeCompletion();
            return null;
        } else if ("afterCompletion".equals(methodName)) {
            afterCompletion(((Integer) args[0]).intValue());
            return null;
        } else if ("isAged".equals(methodName)) {
            return Boolean.valueOf(isAged());
        } else if ("isOpen".equals(methodName)) {
            return Boolean.valueOf(isOpen());
        } else if ("getOpenCount".equals(methodName)) {
            return Integer.valueOf(getOpenCount());
        } else if ("inactive".equals(methodName)) {
            return Boolean.valueOf(inactive());
        } else if ("isClosed".equals(methodName)) {
            return Boolean.valueOf(isClosed());
        } else if ("hold".equals(methodName)) {
            hold();
            return null;
        } else if ("release".equals(methodName)) {
            return Boolean.valueOf(release());
        } else if ("setTx".equals(methodName)) {
            setTx(((Transaction) args[0]));
            return null;
        } else if ("getTx".equals(methodName)) {
            return getTx();
        } else if ("remove".equals(methodName)) {
            remove();
            return null;
        } else if ("addStatementEventListener".equals(methodName) || "removeStatementEventListener".equals(methodName)) {
            throw new UnsupportedOperationException("JDK 6.0 / JDBC 4.0 API Not supported");
        } else if ("notifyPsClose".equals(methodName)) {
            notifyPsClose((JStatement) args[0]);
        } else if ("prepareStatement".equals(methodName)) {
            // Single arg method
            if (args.length == 1) {
                return prepareStatement((String) args[0]);
            }
            // multiple arg method
            return prepareStatement((String) args[0], ((Integer) args[1]).intValue(), ((Integer) args[2]).intValue());
        }


        // Should not go here as we should have redirected all methods
        logger.error("Method ''{0}'' not handled by the proxy", method);
        return null;
    }


    /**
     * @return The identifier of this JManagedConnection
     */
    public int getIdentifier() {
        return identifier;
    }

    /**
     * Dynamically change the prepared statement pool size.
     * @param max the maximum of prepared statement.
     */
    public void setPstmtMax(final int max) {
        pstmtmax = max;
        if (psList == null) {
            psList = Collections.synchronizedMap(new HashMap<String, IPreparedStatement>(pstmtmax));
        }
    }

    /**
     * Commit the global transaction specified by xid.
     * @param xid transaction xid
     * @param onePhase true if one phase commit
     * @param proxy the proxy used to notify errors
     * @throws XAException XA protocol error
     */
    public void commit(final Xid xid, final boolean onePhase, final IManagedConnection proxy) throws XAException {
        logger.debug("XA-COMMIT for {0}", xid);

        // Commit the transaction
        try {
            physicalConnection.commit();
        } catch (SQLException e) {
            logger.error("Cannot commit transaction", e);
            notifyError(proxy, e);
            throw new XAException("Error on commit");
        }
    }

    /**
     * Ends the work performed on behalf of a transaction branch.
     * @param xid transaction xid
     * @param flags currently unused
     * @throws XAException XA protocol error
     */
    public void end(final Xid xid, final int flags) throws XAException {
        logger.debug("XA-END for {0}", xid);
    }

    /**
     * Tell the resource manager to forget about a heuristically completed
     * transaction branch.
     * @param xid transaction xid
     * @throws XAException XA protocol error
     */
    public void forget(final Xid xid) throws XAException {
        logger.debug("XA-FORGET for {0}", xid);
    }

    /**
     * Obtain the current transaction timeout value set for this XAResource
     * instance.
     * @return the current transaction timeout in seconds
     * @throws XAException XA protocol error
     */
    public int getTransactionTimeout() throws XAException {
        logger.debug("getTransactionTimeout for {0}", this);
        return timeout;
    }

    /**
     * Determine if the resource manager instance represented by the target
     * object is the same as the resource manager instance represented by the
     * parameter xares.
     * @param xares An XAResource object
     * @return True if same RM instance, otherwise false.
     * @throws XAException XA protocol error
     */
    public boolean isSameRM(final Object xares) throws XAException {

        // In this pseudo-driver, we must return true only if
        // both objects refer to the same XAResource, and not
        // the same Resource Manager, because actually, we must
        // send commit/rollback on each XAResource involved in
        // the transaction.
        if (xares.equals(this)) {
            logger.debug("isSameRM = true {0}", this);
            return true;
        }
        logger.debug("isSameRM = false {0}", this);
        return false;
    }

    /**
     * Ask the resource manager to prepare for a transaction commit of the
     * transaction specified in xid.
     * @param xid transaction xid
     * @throws XAException XA protocol error
     * @return always OK
     */
    public int prepare(final Xid xid) throws XAException {
        logger.debug("XA-PREPARE for {0}", xid);
        // No 2PC on standard JDBC drivers
        return XAResource.XA_OK;
    }

    /**
     * Obtain a list of prepared transaction branches from a resource manager.
     * @param flag unused parameter.
     * @return an array of transaction Xids
     * @throws XAException XA protocol error
     */
    public Xid[] recover(final int flag) throws XAException {
        logger.debug("XA-RECOVER for {0}", this);
        // Not implemented
        return null;
    }

    /**
     * Inform the resource manager to roll back work done on behalf of a
     * transaction branch.
     * @param xid transaction xid
     * @param proxy the proxy used to notify errors
     * @throws XAException XA protocol error
     */
    public void rollback(final Xid xid, final IManagedConnection proxy) throws XAException {
        logger.debug("XA-ROLLBACK for {0}", xid);

        // Make sure that we are not in AutoCommit mode
        try {
            if (physicalConnection.getAutoCommit()) {
                logger.error("Rollback called on XAResource with AutoCommit set");
                throw (new XAException(XAException.XA_HEURCOM));
            }
        } catch (SQLException e) {
            logger.error("Cannot getAutoCommit", e);
            notifyError(proxy, e);
            throw (new XAException("Error on getAutoCommit"));
        }

        // Rollback the transaction
        try {
            physicalConnection.rollback();
        } catch (SQLException e) {
            logger.error("Cannot rollback transaction", e);
            notifyError(proxy, e);
            throw (new XAException("Error on rollback"));
        }
    }

    /**
     * Set the current transaction timeout value for this XAResource instance.
     * @param seconds timeout value, in seconds.
     * @return always true
     * @throws XAException XA protocol error
     */
    @SuppressWarnings("boxing")
    public boolean setTransactionTimeout(final int seconds) throws XAException {
        logger.debug("setTransactionTimeout to {0} for {1}", seconds, this);
        timeout = seconds;
        return true;
    }

    /**
     * Start work on behalf of a transaction branch specified in xid.
     * @param xid transaction xid
     * @param flags unused parameter
     * @throws XAException XA protocol error
     */
    public void start(final Xid xid, final int flags) throws XAException {
        logger.debug("XA-START for {0}", xid);
    }

    /**
     * Compares this object with another specified object.
     * @param current the current managed connection (proxy)
     * @param other the object to compare
     * @return a value detecting if these objects are matching or not.
     */
    public int compareTo(final IManagedConnection current, final IManagedConnection other) {
        int diff = current.getReUsedPreparedStatements() - other.getReUsedPreparedStatements();
        if (diff == 0) {
            return current.getIdentifier() - other.getIdentifier();
        }
        return diff;
    }

    /**
     * @return value of reused prepared statement.
     */
    public int getReUsedPreparedStatements() {
        return reUsedPreparedStatements;
    }

    /**
     * Create an object handle for a database connection.
     * @exception SQLException - if a database-access error occurs
     * @return connection used by this managed connection
     */
    public IConnection getConnection() throws SQLException {
        // Just return the already created object.
        return implConn;
    }

    /**
     * Close the database connection.
     * @exception SQLException - if a database-access error occurs
     */
    public void close() throws SQLException {

        // Close the actual Connection here.
        if (physicalConnection != null) {
            physicalConnection.close();
        } else {
            logger.error("Connection already closed. Stack of this new close()", new Exception());
        }
        physicalConnection = null;
        implConn = null;
    }

    /**
     * Add an event listener.
     * @param listener event listener
     */
    public void addConnectionEventListener(final ConnectionEventListener listener) {
        eventListeners.addElement(listener);
    }

    /**
     * Remove an event listener.
     * @param listener event listener
     */
    public void removeConnectionEventListener(final ConnectionEventListener listener) {
        eventListeners.removeElement(listener);
    }

    /**
     * synchronization implementation. {@inheritDoc}
     */
    public void beforeCompletion() {
        // nothing to do
    }

    /**
     * synchronization implementation. {@inheritDoc}
     */
    public void afterCompletion(final int status) {
        if (tx != null) {
            ds.freeConnections(tx);
        } else {
            logger.error("NO TX!");
        }
    }

    /**
     * @return true if connection max age has expired
     */
    public boolean isAged() {
        return (deathTime < System.currentTimeMillis());
    }

    /**
     * @return true if connection is still open
     */
    public boolean isOpen() {
        return (open > 0);
    }

    /**
     * @return open count
     */
    public int getOpenCount() {
        return open;
    }

    /**
     * Check if the connection has been unused for too long time. This occurs
     * usually when the caller forgot to call close().
     * @return true if open time has been reached, and not involved in a tx.
     */
    public boolean inactive() {
        return (open > 0 && tx == null && closeTime < System.currentTimeMillis());
    }

    /**
     * @return true if connection is closed
     */
    public boolean isClosed() {
        return (open <= 0);
    }

    /**
     * Notify as opened.
     */
    public void hold() {
        open++;
        closeTime = System.currentTimeMillis() + ds.getMaxOpenTimeMilli();
    }

    /**
     * notify as closed.
     * @return true if normal close.
     */
    public boolean release() {
        open--;
        if (open < 0) {
            logger.warn("connection was already closed");
            open = 0;
            return false;
        }
        if (tx == null && open > 0) {
            logger.error("connection-open counter overflow");
            open = 0;
        }
        return true;
    }

    /**
     * Set the associated transaction.
     * @param tx Transaction
     */
    public void setTx(final Transaction tx) {
        this.tx = tx;
    }

    /**
     * @return the Transaction
     */
    public Transaction getTx() {
        return tx;
    }

    /**
     * remove this item, ignoring exception on close.
     */
    public void remove() {
        // Close the physical connection
        try {
            close();
        } catch (java.sql.SQLException ign) {
            logger.error("Could not close Connection: ", ign);
        }

        // remove all references (for GC)
        tx = null;

    }

    // -----------------------------------------------------------------
    // Other methods
    // -----------------------------------------------------------------

    /**
     * Try to find a PreparedStatement in the pool for the given options.
     * @param sql the sql of the prepared statement
     * @param resultSetType the type of resultset
     * @param resultSetConcurrency the concurrency of this resultset
     * @return a preparestatement object
     * @throws SQLException if an errors occurs on the database.
     */
    private PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
            throws SQLException {

        logger.debug("sql = {0}", sql);
        if (pstmtmax == 0) {
            return physicalConnection.prepareStatement(sql, resultSetType, resultSetConcurrency);
        }
        IPreparedStatement ps = null;
        synchronized (psList) {
            ps = psList.get(sql);
            if (ps != null) {
                if (!ps.isClosed()) {
                    logger.warn("reuse an open pstmt");
                }
                ps.reuse();
                reUsedPreparedStatements++;
            } else {
                // Not found in cache. Create a new one.
                PreparedStatement aps = physicalConnection.prepareStatement(sql, resultSetType, resultSetConcurrency);
                ps = (IPreparedStatement) Proxy.newProxyInstance(IPreparedStatement.class.getClassLoader(),
                        new Class[] {IPreparedStatement.class}, new JStatement(aps, this, sql));

                psList.put(sql, ps);
            }
            psOpenNb++;
        }
        return ps;
    }

    /**
     * Try to find a PreparedStatement in the pool.
     * @param sql the given sql query.
     * @throws SQLException if an error in the database occurs.
     * @return a given prepared statement.
     */
    public PreparedStatement prepareStatement(final String sql) throws SQLException {
        return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    }

    /**
     * A PreparedStatement has been logically closed.
     * @param ps a prepared statement.
     */
    public void notifyPsClose(final JStatement ps) {
        logger.debug(ps.getSql());
        synchronized (psList) {
            psOpenNb--;
            if (psList.size() >= pstmtmax) {
                // Choose a closed element to remove.
                IPreparedStatement lru = null;
                Iterator<IPreparedStatement> i = psList.values().iterator();
                while (i.hasNext()) {
                    lru = i.next();
                    if (lru.isClosed()) {
                        // actually, remove the first closed element.
                        i.remove();
                        lru.forget();
                        break;
                    }
                }
            }
        }
    }

    /**
     * Notify a Close event on Connection.
     * @param proxy the proxy used to create events
     */
    @SuppressWarnings("boxing")
    public void notifyClose(final IManagedConnection proxy) {

        // Close all PreparedStatement not already closed
        // When a Connection has been closed, no PreparedStatement should
        // remain open. This can avoids lack of cursor on some databases.
        synchronized (psList) {
            if (psOpenNb > 0) {
                IPreparedStatement jst = null;
                Iterator<IPreparedStatement> i = psList.values().iterator();
                while (i.hasNext()) {
                    jst = i.next();
                    if (jst.forceClose()) {
                        psOpenNb--;
                    }
                }
                if (psOpenNb != 0) {
                    logger.warn("Bad psOpenNb value = {0}", psOpenNb);
                    psOpenNb = 0;
                }
            }
        }

        // Notify event to listeners
        for (int i = 0; i < eventListeners.size(); i++) {
            ConnectionEventListener l = eventListeners.elementAt(i);
            l.connectionClosed(new ConnectionEvent(proxy));
        }
    }

    /**
     * Notify an Error event on Connection.
     * @param proxy the proxy used to create events
     * @param ex the given exception
     */
    public void notifyError(final IManagedConnection proxy, final SQLException ex) {
        // Notify event to listeners
        for (int i = 0; i < eventListeners.size(); i++) {
            ConnectionEventListener l = eventListeners.elementAt(i);
            l.connectionErrorOccurred(new ConnectionEvent(proxy, ex));
        }
    }


}
TOP

Related Classes of org.ow2.easybeans.component.jdbcpool.JManagedConnection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.