package com.ahtik;

import net.sf.hibernate.FlushMode;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.TransactionException;
import net.sf.hibernate.engine.SessionImplementor;
import net.sf.hibernate.transaction.JDBCTransaction;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;

import java.sql.SQLException;

/**
 * Transaction that works exactly like JDBCTransaction except that commits are always
 * ignored and instead of commit session.flush() is called.
 * 
 * @author ahti.kitsik@gmail.com
 *
 */
public class RollbackTransaction implements Transaction {

  private final static Logger LOG = Logger.getLogger(RollbackTransaction.class);

  private SessionImplementor session;

  private boolean toggleAutoCommit;

  private boolean rolledBack;

  private boolean committed;

  private boolean begun;

  private boolean commitFailed;

  private static final Log log = LogFactory.getLog(JDBCTransaction.class);

  public RollbackTransaction(SessionImplementor session)
      throws HibernateException {
    this.session = session;
  }

  public void begin() throws HibernateException {

    log.debug("begin");

    try {
      toggleAutoCommit = session.connection().getAutoCommit();
      if (log.isDebugEnabled())
        log.debug("current autocommit status:" + toggleAutoCommit);
      if (toggleAutoCommit) {
        log.debug("disabling autocommit");
        session.connection().setAutoCommit(false);
      }
    } catch (SQLException e) {
      log.error("Begin failed", e);
      throw new TransactionException("Begin failed with SQL exception: ", e);
    }

    begun = true;
  }

  public void commit() throws HibernateException {
    LOG.debug("Executing fake-commit (only session.flush() is invoked.");
    // Avoiding commit but will flush to emulated proper db writing for active connection.          
    if (!begun)
      throw new TransactionException("Transaction not successfully started");
    log.debug("commit");

    if (session.getFlushMode() != FlushMode.NEVER)
      session.flush();

    committed = true;
    session.afterTransactionCompletion(true);
    toggleAutoCommit();
  }

  public void rollback() throws HibernateException {

    if (!begun)
      throw new TransactionException("Transaction not successfully started");

    log.debug("rollback");

    if (!commitFailed) {
      try {
        session.connection().rollback();
        rolledBack = true;
      } catch (SQLException e) {
        log.error("Rollback failed", e);
        throw new TransactionException("Rollback failed with SQL exception: ",
            e);
      } finally {
        session.afterTransactionCompletion(false);
        toggleAutoCommit();
      }
    }
  }

  private void toggleAutoCommit() {
    try {
      if (toggleAutoCommit) {
        log.debug("re-enabling autocommit");
        session.connection().setAutoCommit(true);
      }
    } catch (Exception sqle) {
      log.error("Could not toggle autocommit", sqle);
      //swallow it (the transaction _was_ successful or successfully rolled back)
    }
  }

  public boolean wasRolledBack() {
    return rolledBack;
  }

  public boolean wasCommitted() {
    return committed;
  }

}
