/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.ejb3.tx;

import javax.ejb.EJBException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.jboss.aop.advice.Interceptor;
import org.jboss.aop.joinpoint.Invocation;
import org.jboss.aop.util.PayloadKey;
import org.jboss.ejb3.Container;
import org.jboss.ejb3.EJBContainerInvocation;
import org.jboss.ejb3.stateful.StatefulBeanContext;
import org.jboss.logging.Logger;
import org.jboss.tm.TxUtils;

/**
 * Comment
 *
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 *  @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
 * @version $Revision: 57207 $
 */
public class BMTInterceptor implements Interceptor
{
   private TransactionManager tm;
   private boolean isStateless;
   protected static Logger log = Logger.getLogger(BMTInterceptor.class);

   public BMTInterceptor(TransactionManager tm, boolean stateless)
   {
      this.tm = tm;
      isStateless = stateless;
   }


   public String getName()
   {
      return BMTInterceptor.class.getName();
   }



   public Object handleStateless(Invocation invocation) throws Throwable
   {
      Container container = (Container)invocation.getAdvisor();
      boolean exceptionThrown = false;
      try
      {
         return invocation.invokeNext();
      }
      catch (Exception ex)
      {
         exceptionThrown = true;
         checkStatelessDone(container, ex);
         throw ex;
      }
      finally
      {
         try
         {
            if (!exceptionThrown) checkStatelessDone(container, null);
         }
         finally
         {
            tm.suspend();
         }
      }
   }

   public Object handleStateful(Invocation invocation) throws Throwable
   {
      EJBContainerInvocation ejb = (EJBContainerInvocation)invocation;
      Container container = (Container)invocation.getAdvisor();

      StatefulBeanContext ctx = (StatefulBeanContext)ejb.getBeanContext();
      Transaction tx = (Transaction)ctx.getMetaData().getMetaData("TX", "TX");
      if (tx != null)
      {
         ctx.getMetaData().addMetaData("TX", "TX", null, PayloadKey.TRANSIENT);
         tm.resume(tx);
      }
      try
      {
         return invocation.invokeNext();
      }
      finally
      {
         checkBadStateful(container);
         Transaction newTx = tm.getTransaction();
         if (newTx != null)
         {
            ctx.getMetaData().addMetaData("TX", "TX", newTx, PayloadKey.TRANSIENT);
            tm.suspend();
         }
         else
         {
            ctx.getMetaData().addMetaData("TX", "TX", null, PayloadKey.TRANSIENT);
         }
      }
   }

   public Object invoke(Invocation invocation) throws Throwable
   {
      Transaction oldTx = tm.getTransaction();
      if (oldTx != null) tm.suspend();

      try
      {
         if (isStateless) return handleStateless(invocation);
         else return handleStateful(invocation);
      }
      finally
      {
         if (oldTx != null) tm.resume(oldTx);
      }


   }

   private void checkStatelessDone(Container container, Exception ex)
   {
      int status = Status.STATUS_NO_TRANSACTION;

      try
      {
         status = tm.getStatus();
      }
      catch (SystemException sex)
      {
         log.error("Failed to get status", sex);
      }

      switch (status)
      {
         case Status.STATUS_ACTIVE :
         case Status.STATUS_COMMITTING :
         case Status.STATUS_MARKED_ROLLBACK :
         case Status.STATUS_PREPARING :
         case Status.STATUS_ROLLING_BACK :
            try
            {
               tm.rollback();
            }
            catch (Exception sex)
            {
               log.error("Failed to rollback", sex);
            }
         // fall through...
         case Status.STATUS_PREPARED :
            String msg = "Application error: BMT stateless bean " + container.getEjbName()
                         + " should complete transactions before" + " returning (ejb1.1 spec, 11.6.1)";
            log.error(msg);

            // the instance interceptor will discard the instance
            if (ex != null)
            {
               if (ex instanceof EJBException)
                  throw (EJBException)ex;
               else
                  throw new EJBException(msg, ex);
            }
            else throw new EJBException(msg);
      }
   }

   private void checkBadStateful(Container container)
   {
      int status = Status.STATUS_NO_TRANSACTION;

      try
      {
         status = tm.getStatus();
      }
      catch (SystemException ex)
      {
         log.error("Failed to get status", ex);
      }

      switch (status)
      {
         case Status.STATUS_COMMITTING :
         case Status.STATUS_MARKED_ROLLBACK :
         case Status.STATUS_PREPARING :
         case Status.STATUS_ROLLING_BACK :
            try
            {
               tm.rollback();
            }
            catch (Exception ex)
            {
               log.error("Failed to rollback", ex);
            }
            String msg = "BMT stateful bean '" + container.getEjbName()
                         + "' did not complete user transaction properly status=" + TxUtils.getStatusAsString(status);
            log.error(msg);
      }
   }


}
