/*
 * JORAM: Java(TM) Open Reliable Asynchronous Messaging
 * Copyright (C) 2001 - 2019 ScalAgent Distributed Technologies
 * Copyright (C) 2004 Bull SA
 * Copyright (C) 1996 - 2000 Dyade
 *
 * 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.
 *
 * Initial developer(s): Frederic Maistre (INRIA)
 * Contributor(s): ScalAgent Distributed Technologies
 */
package org.ow2.joram.jakarta.jms;

import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;

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

import fr.dyade.aaa.common.Debug;

/**
 * A <code>XAResource</code> instance is used by a <code>XASession</code> 
 * instance as a delegate to a Transaction Manager.
 */
public class XAResource implements javax.transaction.xa.XAResource {
  /** <code>true</code> if the resource is enlisted in a transaction. */
  private boolean enlisted = false;
  /** The current transaction identifier. */
  private Xid currentXid = null;

  /** The XA connection acting as resource manager. */
  XAResourceMngr rm;

  /** The session producing and consuming messages. */
  Session sess;

  private static Logger logger = Debug.getLogger(XAResource.class.getName());

  /**
   * Constructs an XA resource representing a given session.
   */
  public XAResource(XAResourceMngr rm, Session sess) {
    this.rm = rm;
    this.sess = sess;

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG,
                 " XAResource rm = " + rm + ", sess = " + sess);
  }

  /**
   * Enlists this resource in a given transaction.
   *
   * @exception XAException  If the resource is already enlisted in a
   *                         transaction, or if the RM fails to enlist the
   *                         resource.
   */
  public void start(Xid xid, int flag) throws XAException {
    if (enlisted)
      throw new XAException("Resource already enlisted.");

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG,
                 this + ": start(" + xid + ", " + flag + ")");

    rm.start(xid, flag, sess);

    enlisted = true;
    currentXid = xid;
  }

  /**
   * Delists this resource.
   *
   * @exception XAException  If the resource is not enlisted in the specified
   *                         transaction, or if the RM fails to delist the
   *                         resource.
   */
  public void end(Xid xid, int flag) throws XAException {
    if (! enlisted || ! xid.equals(currentXid))
      throw new XAException("Resource is not enlisted in specified transaction.");

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG,
                 this + ": end(" + xid + ", " + flag + ")");

    rm.end(xid, flag, sess);

    enlisted = false;
    currentXid = null;
  }

  /**
   * Prepares the resource.
   *
   * @exception XAException  If the RM fails to prepare the resource.
   */
  public int prepare(Xid xid) throws XAException {

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG,
                 this + ": prepare(" + xid + ")");
    rm.prepare(xid);
    return XA_OK;
  }

  /**
   * Commits the resource.
   *
   * @exception XAException  If the RM fails to commit the resource.
   */
  public void commit(Xid xid, boolean onePhase) 
  throws XAException {

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG,
                 this + ": commit(" + xid + 
                 ", " + onePhase + ")");

    if (onePhase)
      rm.prepare(xid);

    rm.commit(xid);
  }

  /**
   * Rolls the resource back.
   *
   * @exception XAException  If the RM fails to roll the resource back.
   */
  public void rollback(Xid xid) throws XAException {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG,
                 this + ": rollback(" + xid + ")");

    if (enlisted && currentXid.equals(xid)) {
      rm.end(xid, javax.transaction.xa.XAResource.TMFAIL, sess);
      enlisted = false;
      currentXid = null;
    }

    rm.rollback(xid);
  }

  /** 
   * Recovers the prepared transactions identifiers.
   *
   * @exception XAException  If the RM fails to recover.
   */
  public Xid[] recover(int flag) throws XAException {
    return rm.recover(flag);
  }

  /** 
   * Not implemented as transactions are not heuristically completed.
   *
   * @exception XAException  Always thrown.
   */
  public void forget(Xid xid) throws XAException {
    throw new XAException("Non implemented method.");
  }

  /**
   * Returns <code>false</code> as timeout feature is not supported.
   *
   * @exception XAException  Never thrown.
   */
  public boolean setTransactionTimeout(int seconds) throws XAException {
    return false;
  }

  /**
   * Returns 0 as timeout feature is not supported.
   *
   * @exception XAException  Never thrown.
   */
  public int getTransactionTimeout() throws XAException {
    return 0;
  }

  /** 
   * Checks if this resource shares the same resource manager
   * (XAConnection) with an other resource.
   *
   * @exception XAException  Never thrown.
   */
  public boolean isSameRM(javax.transaction.xa.XAResource o) throws XAException {
    if (! (o instanceof org.ow2.joram.jakarta.jms.XAResource))
      return false;

    XAResource other = (org.ow2.joram.jakarta.jms.XAResource) o;

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG,
                 this + ": isSameRM  other.rm = " + other.rm + ", this.rm = " + this.rm + ", equals = " + this.rm.equals(other.rm));

    return this.rm.equals(other.rm);
  }
}
