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

import java.sql.Connection;
import java.sql.SQLException;
import javax.transaction.xa.*;
import org.objectweb.jonas.common.Log;
import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.api.BasicLevel;

/**
 * This is the implementation of the XAResource interface for our pseudo
 * JDBC-XA driver. This object is used to demarcate the transactions and
 * associate an XID to the SQL requests (start-end).
 * Actually, no real 2PC is done here because the std JDBC driver do not support
 * a java XA interface for it (only commit/rollback but no prepare!)
 * So we choose to return always OK at prepare. The goal is more to offer a
 * standard JDBC-XA interface to the EJB Container than to achieve a real
 * distributed 2PC. Anyway, when database vendors will offer real JDBC-XA
 * drivers, the EJBServer will be OK without any change.
 * @author Philippe Durieux
 * Contributor(s): 
 */
public class XAResourceImpl implements XAResource {

    static private Logger logger = null;

    private Connection actConn = null;
    private XAConnectionImpl xac = null;
    private int timeout;
    private Xid currXid = null;
    private boolean xa_started = false;
    private String rmid = null;

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

    public XAResourceImpl(XAConnectionImpl xac, Connection actual, XADataSourceImpl ds) {
	this.xac = xac;
	this.actConn = actual;
	this.rmid = ds.getDataSourceName();
        logger = Log.getLogger(Log.JONAS_JDBCXA_PREFIX);
        logger.log(BasicLevel.DEBUG,"new object for "+this);
    }

    // -----------------------------------------------------------------
    // XAResource implementation
    // -----------------------------------------------------------------

    /**
     * Commit the global transaction specified by xid. 
     */
    public synchronized void commit(Xid xid, boolean onePhase) throws XAException {

	logger.log(BasicLevel.DEBUG, "XA-COMMIT for "+this);
	logger.log(BasicLevel.DEBUG, "Xid = "+xid);

	// Make sure that we do not mix transactions
	// Since start and end are not sent to database, we must check here
	// the xids.
	if (currXid != null && !currXid.equals(xid)) {
	    logger.log(BasicLevel.DEBUG, "mixed xids: "+xid+" != "+currXid);
	    XAException ex = new XAException("cannot mix transactions");
	    throw(ex);
	}
	currXid = null;		// just for cleaning
	if (xa_started) {
	    // Unbalance start/end. Probably a close is missing.
	    // We decide to unset xa_start here to avoid a further error, but
	    // I'm not sure it's a good idea.
	    logger.log(BasicLevel.WARN, "XA START without XA END");
            
	    xa_started = false;
	}

	// Commit the transaction
	try {
	    actConn.commit();
	} catch (SQLException e) {
	    logger.log(BasicLevel.ERROR, "Cannot commit transaction:"+e);
	    xac.notifyError(e);
	    throw(new XAException("Error on commit"));
	}
    }

    /**
     *  Ends the work performed on behalf of a transaction branch.
     */
    public synchronized void end(Xid xid, int flags) throws XAException {

	logger.log(BasicLevel.DEBUG, "XA-END for "+this);
	logger.log(BasicLevel.DEBUG, "Xid = "+xid);

	// Check that we got a start on this Xid first.
	if (currXid == null || !currXid.equals(xid)) {
	    logger.log(BasicLevel.WARN, "END without START or mixed xids: "+xid+" != "+currXid);
	    XAException ex = new XAException(XAException.XA_RBPROTO);
	    throw(ex);
	}
	xa_started = false;
    }

    /**
     * Tell the resource manager to forget about a heuristically 
     * completed transaction branch.
     */
    public synchronized void forget(Xid xid) throws XAException {

	// not implemented. XXX
	logger.log(BasicLevel.DEBUG, "XA-FORGET for "+this);
	logger.log(BasicLevel.DEBUG, "Xid = "+xid);
	currXid = null;
    }

    /**
     * Obtain the current transaction timeout value set for this 
     * XAResource instance.
     */
    public synchronized int getTransactionTimeout() throws XAException {

	logger.log(BasicLevel.DEBUG, "getTransactionTimeout for "+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.
     */
    public boolean isSameRM(XAResource 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.log(BasicLevel.DEBUG, "isSameRM = true "+this);
	    return true;
	}
	logger.log(BasicLevel.DEBUG, "isSameRM = false "+this);
	return false;
    }


    /**
     * Ask the resource manager to prepare for a transaction commit 
     * of the transaction specified in xid.
     */
    public synchronized int prepare(Xid xid) throws XAException {

	logger.log(BasicLevel.DEBUG, "XA-PREPARE for "+this);
	logger.log(BasicLevel.DEBUG, "Xid = "+xid);
	// No 2PC on standard JDBC drivers
	return XA_OK;
    }

    /**
     * Obtain a list of prepared transaction branches from a resource 
     * manager.
     */
    public synchronized Xid[] recover(int flag) throws XAException {

	logger.log(BasicLevel.DEBUG, "XA-RECOVER for "+this);
	// Not implemented
	return null;
    }
    
    /** 
     * Inform the resource manager to roll back work done on behalf 
     * of a transaction branch
     */
    public synchronized void rollback(Xid xid) throws XAException {

	logger.log(BasicLevel.DEBUG, "XA-ROLLBACK for "+this);
	logger.log(BasicLevel.DEBUG, "Xid = "+xid);

	// Make sure that we do not mix transactions
	// Since start and end are not sent to database, we must check here
	// the xids.
	if (currXid != null && !currXid.equals(xid)) {
	    logger.log(BasicLevel.DEBUG, "mixed xids: "+xid+" != "+currXid);
	    XAException ex = new XAException("Cannot mix transactions");
	    throw(ex);
	}
	currXid = null;		// just for cleaning
	if (xa_started) {
	    // Unbalance start/end. Probably a close is missing.
	    // We decide to unset xa_start here to avoid a further error, but
	    // I'm not sure it's a good idea.
	    logger.log(BasicLevel.WARN, "XA START without XA END");
	    xa_started = false;
	}

	// Make sure that we are not in AutoCommit mode
	try {
	    if (actConn.getAutoCommit() == true) {
		logger.log(BasicLevel.ERROR, "Rollback called on XAResource with AutoCommit set");
		throw (new XAException(XAException.XA_HEURCOM));
	    }
	} catch (SQLException e) {
	    logger.log(BasicLevel.ERROR, "Cannot getAutoCommit:"+e);
	    xac.notifyError(e);
	    throw(new XAException("Error on getAutoCommit"));
	}
	
	// Rollback the transaction
	try {
	    actConn.rollback();
	} catch (SQLException e) {
	    logger.log(BasicLevel.ERROR, "Cannot rollback transaction:"+e);
	    xac.notifyError(e);
	    throw(new XAException("Error on rollback"));
	}
    }

    /** 
     * Set the current transaction timeout value for this XAResource
     * instance.
     */
    public synchronized boolean setTransactionTimeout(int seconds) throws XAException {

	logger.log(BasicLevel.DEBUG, "setTransactionTimeout "+this);
	timeout = seconds;
	return true;
    }

    /**
     * Start work on behalf of a transaction branch specified in xid
     */
    public synchronized void start(Xid xid, int flags) throws XAException {

	logger.log(BasicLevel.DEBUG, "XA-START for "+this);
	logger.log(BasicLevel.DEBUG, "Xid = "+xid);
	if (xa_started) {
	    logger.log(BasicLevel.DEBUG, "mixed xids: "+xid+" != "+currXid);
	    XAException ex = new XAException("XA START: mixed transactions");
	    throw ex;
	}
	currXid = xid;
	xa_started = true;
    }

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

    /*
     * get the RMID
     */
    public String getRMID() {
	return rmid;
    }
}
