/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 Bull S.A.
 * Contact: jonas-team@objectweb.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$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas.dbm;

import java.util.Map;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;


import org.objectweb.jonas.common.Log;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

public class JConnection implements Connection {

    static private Logger logger = Log.getLogger(Log.JONAS_DBM_PREFIX + ".con");

    protected Connection actConn = null;

    protected JManagedConnection xac = null;

    protected boolean autocommit_set = false;

    protected boolean autocommit_unset = false;

    protected boolean checkclose = true;

    static private Class implclass = null;

    // -----------------------------------------------------------------
    // Constructors
    // -----------------------------------------------------------------

    /**
     */
    public JConnection(JManagedConnection xac, Connection actual) {
        this.xac = xac;
        actConn = actual;
    }

    // -----------------------------------------------------------------
    // Accessors
    // -----------------------------------------------------------------

    /**
     * Get the actual connection on database
     */
    public Connection getConnection() {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "");
        }
        return actConn;
    }

    // -----------------------------------------------------------------
    // Connection implementation
    // Most of the methods just forward the call to the actual connection.
    // -----------------------------------------------------------------

    public Statement createStatement() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            Thread.dumpStack();
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.createStatement();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public PreparedStatement prepareStatement(String sql) throws SQLException {
        logger.log(BasicLevel.DEBUG, sql);
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            // Ask the Managed Connection to find one in the pool, if possible.
            return xac.prepareStatement(sql);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public CallableStatement prepareCall(String sql) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.prepareCall(sql);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public String nativeSQL(String sql) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.nativeSQL(sql);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public boolean isPhysicallyClosed() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        return actConn.isClosed();
    }

    public boolean isClosed() throws SQLException {
        return xac.isClosed();
    }

    public void setCheckClose(boolean cc) {
        checkclose = cc;
    }

    public DatabaseMetaData getMetaData() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            return actConn.getMetaData();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public void setReadOnly(boolean readOnly) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            actConn.setReadOnly(readOnly);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public boolean isReadOnly() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            return actConn.isReadOnly();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public void setCatalog(String catalog) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            actConn.setCatalog(catalog);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public String getCatalog() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            return actConn.getCatalog();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    /**
     * Trigger an event to the listener.
     */
    public void close() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        xac.notifyClose();
    }

    public void setTransactionIsolation(int level) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            actConn.setTransactionIsolation(level);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public int getTransactionIsolation() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            return actConn.getTransactionIsolation();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public SQLWarning getWarnings() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            return actConn.getWarnings();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public void clearWarnings() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            actConn.clearWarnings();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    /**
     * In a JDBC-XA driver, Connection.commit is only called if we are outside a
     * global transaction.
     */
    public void commit() throws SQLException {
        logger.log(BasicLevel.DEBUG, "local transaction");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            actConn.commit();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    /**
     * In a JDBC-XA driver, Connection.rollback is only called if we are outside
     * a global transaction.
     */
    public void rollback() throws SQLException {
        logger.log(BasicLevel.DEBUG, "local transaction");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            actConn.rollback();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    /**
     * In a JDBC-XA driver, autocommit is false if we are in a global Tx
     */
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            if (autoCommit == false) {
                if (autocommit_unset == false) { // cache for optimization
                    actConn.setAutoCommit(false);
                    autocommit_set = false;
                    autocommit_unset = true;
                }
            } else {
                if (autocommit_set == false) { // cache for optimization
                    actConn.setAutoCommit(true);
                    autocommit_set = true;
                    autocommit_unset = false;
                }
            }
        } catch (SQLException e) {
            String s = e.getMessage().toLowerCase();
            if (s.indexOf("set chained command not allowed") != -1) {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "failed...");
                    logger.log(BasicLevel.DEBUG, "Committing then retrying");
                }
                try {
                    // These lines for Sybase only, hoping they don't broke
                    // anything for others DBs.
                    actConn.commit();
                    actConn.setAutoCommit(autoCommit); // Shouldn't fail now.
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "succeeded after retry");
                    }
                } catch (SQLException se) {
                    logger.log(BasicLevel.ERROR, "" + autoCommit + ") failed again after retry", se);
                    xac.notifyError(e);
                    throw (e);
                }
            } else {
                logger.log(BasicLevel.ERROR, "setAutoCommit(" + autoCommit + ") failed: " + e);
                xac.notifyError(e);
                throw (e);
            }
        }
    }

    /**
     * In a JDBC-XA driver, autocommit is false if we are in a global Tx
     */
    public boolean getAutoCommit() throws SQLException {
        try {
            return actConn.getAutoCommit();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.createStatement(resultSetType, resultSetConcurrency);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public Map getTypeMap() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            return actConn.getTypeMap();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public void setTypeMap(Map map) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            setTypeMap(map);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
            throws SQLException {
        logger.log(BasicLevel.DEBUG, sql);
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return xac.prepareStatement(sql, resultSetType, resultSetConcurrency);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.prepareCall(sql, resultSetType, resultSetConcurrency);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
            throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public int getHoldability() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            return actConn.getHoldability();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
            int resultSetHoldability) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        logger.log(BasicLevel.DEBUG, sql);
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.prepareStatement(sql, autoGeneratedKeys);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
            int resultSetHoldability) throws SQLException {
        logger.log(BasicLevel.DEBUG, sql);
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }

        try {
            return actConn.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        logger.log(BasicLevel.DEBUG, sql);
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.prepareStatement(sql, columnIndexes);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        logger.log(BasicLevel.DEBUG, sql);
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.prepareStatement(sql, columnNames);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public void releaseSavepoint(java.sql.Savepoint savepoint) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            actConn.releaseSavepoint(savepoint);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public void rollback(java.sql.Savepoint savepoint) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            actConn.rollback(savepoint);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public void setHoldability(int holdability) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        try {
            actConn.setHoldability(holdability);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public java.sql.Savepoint setSavepoint() throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.setSavepoint();
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

    public java.sql.Savepoint setSavepoint(String name) throws SQLException {
        logger.log(BasicLevel.DEBUG, "");
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.ERROR, "Cannot work with a closed Connection");
            throw new SQLException("Cannot work with a closed Connection");
        }
        try {
            return actConn.setSavepoint(name);
        } catch (SQLException e) {
            xac.notifyError(e);
            throw (e);
        }
    }

}
