/*
 * 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.jtests.clients.exception;

import java.rmi.RemoteException;

import javax.ejb.FinderException;
import javax.ejb.RemoveException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.TransactionRolledbackException;

import junit.framework.Assert;

import org.objectweb.jonas.jtests.beans.beanexc.AccountE;
import org.objectweb.jonas.jtests.beans.beanexc.AccountEHome;
import org.objectweb.jonas.jtests.beans.beanexc.AccountPK;
import org.objectweb.jonas.jtests.beans.beanexc.AccountS;
import org.objectweb.jonas.jtests.beans.beanexc.AppException;

public abstract class A_CatcherEntity extends A_Catcher {

    public A_CatcherEntity(String name) {
        super(name);
    }

    /**
     * return an Account
     */
    public AccountE getAccount(int i) {
        AccountE ac = null;
        try {
            ac = getHome().findByNoAccount(i);
        } catch (FinderException e) {
            fail("FinderException in getAccount :" + e);
        } catch (RemoteException e) {
            fail("RemoteException in getAccount :" + e);
        }
        return ac;
    }

    /**
     * return an AccountS
     * must be defined for common tests (A_Catcher.java)
     */
    public AccountS getAccountS(int i) {
        AccountS acs = null;
        try {
            acs = getHome().findByNoAccount(i);
        } catch (FinderException e) {
            fail("FinderException in getAccountS :" + e);
        } catch (RemoteException e) {
            fail("RemoteException in getAccountS :" + e);
        }
        return acs;
    }

    /**
     * return AccontHome, that can be either BMP or CMP bean.
     */
    public abstract AccountEHome getHome();


    // -----------------------------------------------------
    // ApplicationException
    // -----------------------------------------------------

    /**
     * Test business method throwing an Application exception.
     * It must be caught by the client.
     * Methods runs in the context of the caller's transaction
     * The business method has Mandatory attribute.
     * Test that the transaction has not been marked rollback.
     *
     * Spec 2.0 page 375 (table15)
     */
    public void testApplicationCallerTx2() throws Exception {
        AccountE ac = getAccount(87);
        long oldbalance = ac.getBalance();
        utx.begin();
        try {
            // the method add 5000 to the balance before throwing AppException
            ac.doAppException_1();
            fail("No AppException");
        } catch (AppException e) {
            assertTrue(utx.getStatus() == Status.STATUS_ACTIVE);
            assertEquals("On AppException", 5000, ac.getBalance());
        } finally {
            utx.commit();
            assertEquals("After commit", 5000, ac.getBalance());
        }
    }

    /**
     * Test business method throwing an Application exception.
     * It must be caught by the client.
     * A transaction is started in the container. (RequiresNew)
     * Bean does NOT call setRollbackOnly
     * Test that the transaction has not been marked rollback.
     *
     * Spec 2.0 page 376 (table15)
     */
    public void testApplicationContTx2() throws Exception {
        AccountE ac = getAccount(82);
        long oldbalance = ac.getBalance();
        try {
            // the method add 50 to the balance
            // the instance doesn't call setRollbackOnly
            ac.doAppException_2(false);
            fail("No AppException");
        } catch (AppException e) {
            assertEquals(50, ac.getBalance());
        }
    }

    /**
     * Test business method throwing an Application exception.
     * It must be caught by the client.
     * A transaction is started in the container. (RequiresNew)
     * Bean calls setRollbackOnly
     * Test that the transaction has been rolledback.
     *
     * Spec 2.0 page 376 (table15)
     */
    public void testApplicationContTxRb2() throws Exception {
        AccountE ac = getAccount(82);
        long oldbalance = ac.getBalance();
        try {
            // the method add 50 to the balance
            // the instance calls setRollbackOnly
            ac.doAppException_2(true);
            fail("No AppException");
        } catch (AppException e) {
            assertEquals(oldbalance, ac.getBalance());
        }
    }

    /**
     * Test Create  method throwing an Application exception.
     * It must be caught by the client.
     * Test that the transaction has not been marked rollback.
     *
     * equivalent to testApplicationContTx but the businessmethod is a create method
     *
     * Spec 2.0 page 376 (table15)
     */
    public void testApplicationHomeUserTx() throws Exception {
        utx.begin();
        try {
            // throw AppException
            getHome().create(true);
            fail("No AppException");
        } catch (AppException e) {
        } finally {
            utx.commit();
        }
        try {
            getHome().findByNoAccount(1951);
            fail("bean has been created");
        } catch (FinderException e) {
        }
    }

    /**
     * Test create  method throwing an Application exception.
     * It must be caught by the client.
     * instance call setRollbackOnly
     * The test verify the ac is not created
     *
     * equivalent to testApplicationContTxRb1 but for create method.
     *
     * Spec 2.0 page 376 (table15)
     */
    public void testApplicationHomeContTx() throws Exception {
        try {
            // throw AppException
            getHome().create(0);
            fail("No AppException");
        } catch (AppException e) {
        }
        try {
            getHome().findByNoAccount(1951);
            fail("bean has been created");
        } catch (FinderException e) {
        }
    }

    /**
     * Test home method throwing an Application exception.
     * It must be caught by the client.
     *
     * Spec 2.0 page 376 (table15)
     */
    public void testApplicationHomeNoTx() throws Exception {
        try {
            // throw AppException
            // create(boolean) has the NotSupported trans. attribute.
            // the instance is created with the pk 1951
            getHome().create(true);
            fail("No AppException");
        } catch (AppException e) {
        }
        // No transaction involved -> the bean should be created.
        // Actually, this is not the case. Why should it be created ?
        // AccountE ac = getHome().findByNoAccount(1951);
        // ac.remove();
    }

    /**
     * Test remove(pk) method throwing an Application exception (RemoveException).
     * It must be caught by the client.
     * A transaction is started in the container. (Required)
     *
     * Spec 2.0 page 376 (table15)
     */
    public void testApplicationRemovePkContTx() throws Exception {
        int nac = 999990;
        try {
            AccountPK pk = new AccountPK(nac);
            // throw AppException
            getHome().remove(pk);
            fail("No RemoveException");
        } catch (RemoveException e) {
        }
        // Check if the account nac already exists and had correct values
        AccountE ac = getAccount(nac);
        Assert.assertEquals("For exception in ejbRemove", ac.getCustomer());
    }

    /**
     * Test remove(pk) method throwing an Application exception (RemoveException).
     * It must be caught by the client.
     * A transaction is started in the client. (Required)
     * bug #100265
     * Spec 2.1 page 400 (table15)
     */
    public void testApplicationRemovePkUserTx() throws Exception {
        int nac = 999991;
        utx.begin();
        try {
            AccountPK pk = new AccountPK(nac);
            // throw AppException
            getHome().remove(pk);
            fail("No RemoveException");
        } catch (RemoveException e) {
        } finally {
            utx.commit();
        }
        // Check if the account nac already exists and had correct values
        AccountE ac = getAccount(nac);
        Assert.assertEquals("For exception in ejbRemove", ac.getCustomer());
    }

    /**
     * Test remove() method throwing an Application exception (RemoveException).
     * It must be caught by the client.
     * A transaction is started in the container. (Required)
     * equivalent to testApplicationRemoveHomeContTx but for obj.remove() method.
     *
     * Spec 2.0 page 376 (table15)
     */
    public void testApplicationRemoveThisContTx() throws Exception {
        int nac = 999992;
        AccountE ac;
        try {
            ac = getAccount(nac);
            // throw AppException
            ac.remove();
            fail("No RemoveException");
        } catch (RemoveException e) {
        }
        // Check if the account nac already exists and had correct values
        ac = getAccount(nac);
        Assert.assertEquals("For exception in ejbRemove", ac.getCustomer());
    }

    /**
     * Test remove() method throwing an Application exception (RemoveException).
     * It must be caught by the client.
     * A transaction is started in the client. (Required)
     * equivalent to testApplicationRemoveHomeUserTx but for obj.remove() method.
     * BUG #100265
     * Spec 2.0 page 400 (table15)
     */
    public void testApplicationRemoveThisUserTx() throws Exception {
        int nac = 999993;
        AccountE ac;
        utx.begin();
        try {
            ac = getAccount(nac);
            // throw AppException
            ac.remove();
            fail("No RemoveException");
        } catch (RemoveException e) {
        } finally {
            utx.commit();
        }
        // Check if the account nac already exists and had correct values
        ac = getAccount(nac);
        Assert.assertEquals("For exception in ejbRemove", ac.getCustomer());
    }

    // -----------------------------------------------------
    // RuntimeException
    // -----------------------------------------------------

    /**
     * Test RuntimeException in a container-invoked callback (ejbStore)
     * The bean has the TransationRequired attribute.
     * -> Exception must be logged, instance discarded, and the client should
     *    receive a RemoteException.
     *
     * See EJB2.0  specs 8.3.3 page 379
     */
    public void testRuntimeContTx() throws Exception {
        AccountE ac = getAccount(81);
        try {
            ac.doFailedEjbStore_2();
            fail("ejbStore should raise TransactionRolledbackException");
        } catch (RemoteException e) {
            assertTransactionRolledback(e);
        }
    }

    /**
     * Test RuntimeException in a container-invoked callback (ejbStore)
     * Methods runs in the context of the caller's transaction
     * -> RollbackException should be received by the client on commit.
     *
     * See EJB2.0 specs 8.3.3 page 379
     */
    public void testRuntimeCallerTx() throws Exception {
        AccountE ac = getAccount(81);
        utx.begin();
        ac.doFailedEjbStore_1();
        try {
            utx.commit();
            fail("commit should raise RollbackException");
        } catch (RollbackException e) {
        }
    }

    // -----------------------------------------------------
    // EJBException
    // -----------------------------------------------------

    /**
     * Test business method throwing an EJBException Called inside a
     * containertransaction (Requires).
     * The client must receive RemoteException
     * See EJB 2.1 specs page 400 (table 15).
     * Test also if the instance has been discarded, ie means that the container
     * must not invoke any bussiness methods or container callbacks on this instance.
     * To reproduce the bug #300421.
     */
    public void testDestroyedAfterEJBContTx() throws Exception {
        AccountS ac = getAccountS(101);
        try {
            ac.doEJBException_2();
            fail("No RemoteException");
        } catch (RemoteException e) {
        }
        assertFalse("instance hasn't been destroyed", ac.iAmDestroyed());
    }

    /**
     * Test business method throwing an EJBException Called inside a
     * client transaction (Supports).
     * The client must receive RemoteException
     * See EJB 2.1 specs page 400 (table 15).
     * Test also if the instance has been discarded, ie means that the container
     * must not invoke any bussiness methods or container callbacks on this instance.
     * To reproduce the bug #300421.
     */
    public void testDestroyedAfterEJBUserTx() throws Exception {
        AccountS ac = getAccountS(101);
        utx.begin();
        try {
            try {
                ac.doEJBException_sup();
                fail("No RemoteException");
            } catch (RemoteException e) {
                assertTransactionRolledback(e);
            }
            try {
                assertTrue("instance should be destroyed", ac.iAmDestroyed());
                fail("business method on discarded instance succeeded");
            } catch (RemoteException e) {
            }
        } finally {
            utx.rollback();
        }
    }

    /**
     * Test business method throwing an EJBException Called outside transaction.
     * The client must receive RemoteException
     * See EJB 2.1 specs page 401 (table 15).
     * Test also if the instance has been discarded, ie means that the container
     * must not invoke any bussiness methods or container callbacks on this instance.
     * To reproduce the bug #300421.
     */
    public void testDestroyedAfterEJBNoTx() throws Exception {
        AccountS ac = getAccountS(101);
        try {
            ac.doEJBException_sup();
            fail("No RemoteException");
        } catch (RemoteException e) {
        }
        assertFalse("instance hasn't been destroyed", ac.iAmDestroyed());
    }

    public void testDiscardedInstances() throws Exception {
        AccountS ac = getAccountS(101);
        utx.begin();
        try {
            try {
                ac.doEJBException_sup();
                fail("No RemoteException");
            } catch (RemoteException e) {
                assertTransactionRolledback(e);
            }
            try {
                assertTrue("instance should be destroyed", ac.iAmDestroyed());
                fail("business method on discarded instance succeeded");
            } catch (RemoteException e) {
            }
        } finally {
            utx.rollback();
        }
        try {
            ac.doEJBException_2();
            fail("No RemoteException");
        } catch (RemoteException e) {
        }
        assertFalse("instance hasn't been destroyed (cont tx)", ac.iAmDestroyed());
    }

    // -----------------------------------------------------
    // UncheckedException
    // -----------------------------------------------------

    /**
     * Test ejbCreate  method throwing an unchecked exception.
     * Called inside a containertransaction.
     * The client must receive RemoteException
     *
     * See EJB2.0 specs page 376 (table 15)
     */
    public void testUncheckedHomeContTx() throws Exception {
        try {
            getHome().create(1); // force UncheckedException
            fail("No RemoteException");
        } catch (RemoteException e) {
        }
        // check rollback was effective.
        try {
            AccountE ac = getHome().findByNoAccount(1951);
            fail("rollback should have removed bean");
        } catch (FinderException e) {
        }
    }

    /**
     * Test ejbCreate method throwing an unchecked exception.
     * Methods runs in the context of the caller's transaction
     * A transaction has been started by the client.
     * Called inside a client transaction.
     * TransactionRolledbackException must be caught by the client.
     * The business method has Mandatory attribute.
     *
     * See EJB2.0 specs page 375 (table 15)
     */
    public void testUncheckedHomeUserTx() throws Exception {
        utx.begin();
        try {
            getHome().create(1); // force UncheckedException
            fail("No RemoteException");
        } catch (RemoteException e) {
            assertTransactionRolledback(e);
        } finally {
            utx.rollback();
        }
        // check rollback was effective.
        try {
            getHome().findByNoAccount(1951);
            fail("rollback should have removed bean");
        } catch (FinderException e) {
        }
    }

    private void assertTransactionRolledback(RemoteException e)
        throws RemoteException {
        // throw exception if not TransactionRolledBack exception or if inner exception is not TransactionRolledbackException
        if ((!(e instanceof java.rmi.ServerException)
            || !(e.detail instanceof TransactionRolledbackException)) && !(e instanceof TransactionRolledbackException)) {
            throw e;
        }
    }
}
