/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 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.jdbc;

import java.io.PrintWriter;
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 javax.resource.ResourceException;

import org.objectweb.jonas.resource.MCInfo;
import org.objectweb.jonas.resource.SQLManager;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * The class <b>Connection</b> provides the Connection implementation for
 * encapsulating JDBC connections
 * @author Eric hardesty
*/
public class ConnectionImpl implements Connection {

    protected boolean autocommit_set = false;

    protected boolean autocommit_unset = false;

    public Logger trace = null;
    
    ManagedConnectionImpl mc = null;

    Connection connection = null;

    PrintWriter pw = null;

    long key = 0;

    String user = "";

    private SQLManager conman = null;

    private MCInfo mci = null;

    /**
     * Traces are enabled ?
     */
    private final boolean isDebugging;

    protected ConnectionImpl(ManagedConnectionImpl _mc, Connection _con, long _key, PrintWriter _pw) {
        mc = _mc;
        connection = _con;
        key = _key;
        pw = _pw;
        trace = mc.trace;
        isDebugging = trace.isLoggable(BasicLevel.DEBUG);
        if (connection == null) {
            trace.log(BasicLevel.ERROR, "Init ConnectionImpl with a null Connection");
        }
    }

    /** 
     * @return true if connection is physically closed
     */
    public boolean isPhysicallyClosed() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        return (connection.isClosed());
    }

    public void setJonasInfo(MCInfo _mci, SQLManager _conman) {
        mci = _mci;
        conman = _conman;
    }

    public void setUser() {
        try {
            user = mc.getMetaData().getUserName();
        } catch (Exception ex) {
        }
    }

    // IMPLEMENTATION OF METHODS FROM THE java.sql.Connection INTERFACE //

    public void clearWarnings() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        connection.clearWarnings();
    }

    public void close() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            if (key != 0) {
                mc.close(this);
                key = 0;
            }
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "error", e);
            e.printStackTrace();
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
    }

    public void commit() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
            connection.commit();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
    }

    public Statement createStatement() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "checkContext error", e, "ConnectionImpl", "createStatement");
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.createStatement();
    }

    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.createStatement: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.createStatement(resultSetType, resultSetConcurrency);
    }

    public boolean getAutoCommit() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
            return connection.getAutoCommit();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.getAC error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
    }

    public String getCatalog() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.getCatalog: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.getCatalog();
    }

    // JDK 1.4 SQL
    public int getHoldability() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.getHoldability: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.getHoldability();
    }

    public DatabaseMetaData getMetaData() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.getMetaData: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.getMetaData();
    }

    public int getTransactionIsolation() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.getTransactionIsolation: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.getTransactionIsolation();
    }

    public Map getTypeMap() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.getTypeMap: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.getTypeMap();
    }

    public SQLWarning getWarnings() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.getWarnings: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.getWarnings();
    }

    public boolean isClosed() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        return (key == 0);
    }

    public boolean isReadOnly() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.isReadOnly: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.isReadOnly();
    }

    public String nativeSQL(String sql) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.nativeSQL: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.nativeSQL(sql);
    }

    public CallableStatement prepareCall(String sql) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.prepareCall: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.prepareCall(sql);
    }

    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {

        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.prepareCall: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
    }

    public PreparedStatement prepareStatement(String sql) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.prepareStatement: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        if (conman != null && conman.getMaxPstmtPoolSize() >= 0) {
            return conman.getPStatement(mci, connection, user, sql);
        } else {
            return connection.prepareStatement(sql);
        }
    }

    public PreparedStatement prepareStatement(String sql, int rsettype, int rsetconcurrency) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.prepareStatement: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        if (conman != null && conman.getMaxPstmtPoolSize() >= 0) {
            return conman.getPStatement(mci, connection, user, sql, rsettype, rsetconcurrency);
        } else {
            return connection.prepareStatement(sql, rsettype, rsetconcurrency);
        }
    }

    public void rollback() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
            connection.rollback();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
    }

    // JDK 1.4 SQL

    public PreparedStatement prepareStatement(String sql, int rsettype, int rsetconcurrency, int holdability)
            throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.prepareStatement: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        if (conman != null && conman.getMaxPstmtPoolSize() >= 0) {
            return conman.getPStatement(mci, connection, user, sql, rsettype, rsetconcurrency, holdability);
        } else {
            return connection.prepareStatement(sql, rsettype, rsetconcurrency, holdability);
        }
    }

    // JDK 1.4 SQL

    public PreparedStatement prepareStatement(String sql, int[] t) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.prepareStatement: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        if (conman != null && conman.getMaxPstmtPoolSize() >= 0) {
            return conman.getPStatement(mci, connection, user, sql, t);
        } else {
            return connection.prepareStatement(sql, t);
        }
    }

    // JDK 1.4 SQL
    public PreparedStatement prepareStatement(String sql, String[] t) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.prepareStatement: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        if (conman != null && conman.getMaxPstmtPoolSize() >= 0) {
            return conman.getPStatement(mci, connection, user, sql, t);
        } else {
            return connection.prepareStatement(sql, t);
        }
    }

    // JDK 1.4 SQL
    public PreparedStatement prepareStatement(String sql, int autoGenKeys) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.prepareStatement: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        if (conman != null && conman.getMaxPstmtPoolSize() >= 0) {
            return conman.getPStatement(mci, connection, user, sql, autoGenKeys);
        } else {
            return connection.prepareStatement(sql, autoGenKeys);
        }
    }

    // JDK 1.4 SQL
    public CallableStatement prepareCall(String sql, int rsettype, int rsetconcurrency, int holdability)
            throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.prepareStatement: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.prepareCall(sql, rsettype, rsetconcurrency, holdability);
    }

    // JDK 1.4 SQL
    public Statement createStatement(int rsettype, int rsetconcurrency, int holdability) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.prepareStatement: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        return connection.createStatement(rsettype, rsetconcurrency, holdability);
    }

    // JDK 1.4 SQL
    public void releaseSavepoint(java.sql.Savepoint sp) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
            connection.releaseSavepoint(sp);
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
    }

    // JDK 1.4 SQL
    public void rollback(java.sql.Savepoint sp) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
            connection.rollback(sp);
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
    }

    // JDK 1.4 SQL
    public java.sql.Savepoint setSavepoint() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
            return connection.setSavepoint();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
    }

    // JDK 1.4 SQL
    public java.sql.Savepoint setSavepoint(String name) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
            return connection.setSavepoint(name);
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
    }

    public void setAutoCommit(boolean isauto) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "isauto=" + isauto);
        }
        if (connection == null) {
            trace.log(BasicLevel.ERROR, "No Connection yet !");
            return;
        }
        try {
            try {
                checkContext();

                if (isauto == false) {
                    if (autocommit_unset == false) { // cache for optimization
                        connection.setAutoCommit(isauto);
                        autocommit_set = false;
                        autocommit_unset = true;
                    }
                } else {
                    if (autocommit_set == false) { // cache for optimization
                        connection.setAutoCommit(isauto);
                        autocommit_set = true;
                        autocommit_unset = false;
                    }
                }
            } catch (SQLException e) {
                String s = e.getMessage().toLowerCase();
                if (s.indexOf("set chained command not allowed") != -1) {
                    if (trace.isLoggable(BasicLevel.DEBUG)) {
                        trace.log(BasicLevel.DEBUG, "failed...");
                        trace.log(BasicLevel.DEBUG, "Committing then retrying");
                    }
                    // These lines for Sybase only, hoping they don't broke
                    // anything for others DBs.
                    connection.commit();
                    connection.setAutoCommit(isauto); // Shouldn't fail now.
                    if (trace.isLoggable(BasicLevel.DEBUG)) {
                        trace.log(BasicLevel.DEBUG, "succeeded after retry");
                    }
                } else {
                    // rethrow if other exception
                    trace.log(BasicLevel.WARN, s);
                    throw e;
                }
            }
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.setAC: error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
    }

    public void setCatalog(String catalog) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.setCatalog: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        connection.setCatalog(catalog);
    }

    // JDK 1.4 SQL
    public void setHoldability(int holdability) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.setCatalog: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        connection.setHoldability(holdability);
    }

    public void setReadOnly(boolean readOnly) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.setReadOnly: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        connection.setReadOnly(readOnly);
    }

    public void setTransactionIsolation(int level) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.setTransactionIsolation: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        connection.setTransactionIsolation(level);
    }

    public void setTypeMap(Map map) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            checkContext();
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "ConnectionImpl.setTypeMap: checkContext error", e);
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
        connection.setTypeMap(map);
    }

    private void checkContext() throws Exception {
        if (key == 0) {
            throw new Exception("Connection is closed");
        }
        if (key != mc.getSignature()) {
            if (mc.getSignature() == 0) {
                mc.setSignature(key);
            } else {
                throw new Exception("Connection w/sig(" + key + ") is not currect active Connection ("
                        + mc.getSignature() + ") for " + mc);
            }
        }
    }

    public void setSignature(long sig) {
        key = sig;
    }
}
