/**
 * 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 checkclose = true;

    // -----------------------------------------------------------------
    // 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 {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "conn=" + this);
        }
        if (checkclose && isClosed()) {
            logger.log(BasicLevel.WARN, "Connection already closed");
            return;
        }
        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 {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "local tx. conn=" + this);
        }
        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 {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "local tx. conn=" + this);
        }
        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
     * @param value true to set connection in autocommit mode
     * @throws SQLException Cannot set AutoCommit mode
     */
    public void setAutoCommit(boolean value) throws SQLException {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "conn=" + this + " value= " + value);
        }
        try {
            actConn.setAutoCommit(value);
        } catch (SQLException e) {
            logger.log(BasicLevel.ERROR, "Could not set AutoCommit:" + e);
            xac.notifyError(e);
            throw e;
        }
    }

    /**
     * In a JDBC-XA driver, autocommit is false if we are in a global Tx
     * @return true if autocommit mode is set
     * @throws SQLException Cannot get AutoCommit mode
     */
    public boolean getAutoCommit() throws SQLException {
        try {
            boolean value = actConn.getAutoCommit();
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "conn=" + this + " value= " + value);
            }
            return value;
        } 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 {
            actConn.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);
        }
    }

}
