/*
Copyright 2017 Red Hat, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package org.jboss.as.test.integration.ejb.transaction.cmt.inheritance;

import jakarta.annotation.Resource;
import jakarta.ejb.EJB;
import jakarta.ejb.EJBException;
import jakarta.ejb.TransactionAttribute;
import jakarta.ejb.TransactionAttributeType;
import jakarta.transaction.Transaction;
import jakarta.transaction.TransactionManager;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * <p>
 * Test checking behavior of {@link TransactionAttribute} when inherited
 * by child classes.
 * <p>
 * <b>8.3.7.1</b> Specification of Transaction Attributes with Metadata Annotations<br>
 * If the bean class has superclasses, the following additional rules apply.
 * <ul>
 *   <li>A transaction attribute specified on a superclass S applies to the business methods defined by
 *    S. If a class-level transaction attribute is not specified on S, it is equivalent to specification of
 *    TransactionAttribute(REQUIRED) on S.</li>
 *   <li>A transaction attribute may be specified on a business method M defined by class S to override
 *    for method M the transaction attribute value explicitly or implicitly specified on the class S.</li>
 *   <li>If a method M of class S overrides a business method defined by a superclass of S, the transaction
 *    attribute of M is determined by the above rules as applied to class S.</li>
 *  </ul>
 *
 * @author Ondrej Chaloupka <ochaloup@redhat.com>
 */
@RunWith(Arquillian.class)
public class TransactionAttributeTestCase {

    @Resource(lookup = "java:/TransactionManager")
    private TransactionManager tm;

    @EJB
    private SuperSLSB superClass;

    @EJB
    private ChildSLSB childClass;

    @EJB
    private ChildWithClassAnnotationSLSB childWithClassAnnotation;

    @Deployment
    public static Archive<?> deploy() {
        WebArchive war = ShrinkWrap.create(WebArchive.class, "transaction-attribute-inheritance.war");
        war.addPackage(TransactionAttributeTestCase.class.getPackage());
        war.add(EmptyAsset.INSTANCE, "WEB-INF/beans.xml");
        return war;
    }

    @Test
    public void superClassCheck() throws Exception {
        tm.begin();
        try {
            // active transaction
            superClass.aMethod();
            superClass.bMethod();
            superClass.cMethod();
        } finally {
            tm.rollback();
        }

        tm.begin();
        try {
            // active transaction
            superClass.neverMethod();
            Assert.fail("TransactionAttribute.NEVER expects failure when running"
                    + " within a transactional context of txn: '" + tm.getTransaction() + "'");
        } catch (EJBException ignoreAsExpected) {
        } finally {
            tm.rollback();
        }

        // no active transaction
        superClass.aMethod();
        superClass.bMethod();
        superClass.cMethod();
        superClass.neverMethod();
    }

    /**
     * Tests expects behavior of {@link TransactionAttributeType#REQUIRED}
     */
    @Test
    public void defaultBeanAttributeOverridesParentClassDeclaration() throws Exception {
        tm.begin();
        Transaction testTxn = tm.getTransaction();
        try {
            // active transaction
            Transaction beanTxn = childClass.aMethod();
            Assert.assertNotNull("There has to be started a transaction in the bean", beanTxn);
            Assert.assertEquals("Child method default REQUIRED TransactionAttribute"
                + " has to override the settings of SUPPORTS from super class", testTxn, beanTxn);
        } finally {
            tm.rollback();
        }

        // no active transaction
        Transaction beanTxn = childClass.aMethod();
        Assert.assertNotNull("There has to be started transaction in bean as TransactionAttribute"
            + " should be REQUIRED", beanTxn);
    }

    /**
     * Tests expects behavior of {@link TransactionAttributeType#SUPPORTS}
     */
    @Test
    public void inheritsWhenMethodCalledFromParent() throws Exception {
        tm.begin();
        try {
            // active transaction
            childClass.bMethod();
        } finally {
            tm.rollback();
        }

        // no active transaction
        Assert.assertNull("There can't be active transaction here", tm.getTransaction());
        childClass.bMethod();
    }

    /**
     * Tests expects behavior of {@link TransactionAttributeType#MANDATORY}
     */
    @Test
    public void explicitMethodAttributeOverrides() throws Exception {
        tm.begin();
        try {
            // active transaction
            childClass.cMethod();
        } finally {
            tm.rollback();
        }

        try {
            // no active transaction
            Assert.assertNull("There can't be active transaction here", tm.getTransaction());
            childClass.cMethod();
            Assert.fail("Method called with no active transaction but MANDATORY requires it");
        } catch (EJBException ignoreAsExpected) {
        }
    }

    /**
     * Tests expects behavior of {@link TransactionAttributeType#REQUIRED}
     */
    @Test
    public void defaultBeanAttributeOverridesParentMethodDeclaration() throws Exception {
        tm.begin();
        Transaction testTxn = tm.getTransaction();
        try {
            // active transaction
            Transaction beanTxn = childClass.neverMethod();
            Assert.assertNotNull("There has to be started a transaction in the bean", beanTxn);
            Assert.assertEquals("Child method default REQUIRED TransactionAttribute"
                + " has to override the settings of SUPPORTS from super class", testTxn, beanTxn);
        } finally {
            tm.rollback();
        }

        // no active transaction
        Assert.assertNull("There can't be active transaction here", tm.getTransaction());
        Transaction beanTxn = childClass.neverMethod();
        Assert.assertNotNull("There has to be started a transaction in the bean "
            + "as TransactionAttribute is REQUIRED", beanTxn);
    }

    /**
     * Tests expects behavior of {@link TransactionAttributeType#NEVER}
     */
    @Test
    public void classAnnotationOverridesParentDeclaration() throws Exception {
        tm.begin();
        try {
            // active transaction
            childWithClassAnnotation.aMethod();
            Assert.fail("TransactionAttribute.NEVER expects failure when running"
                + " within a transactional context of txn: '" + tm.getTransaction() + "'");
        } catch (EJBException ignoreAsExpected) {
        } finally {
            tm.rollback();
        }

        // no active transaction
        Assert.assertNull("There can't be active transaction here", tm.getTransaction());
        childWithClassAnnotation.aMethod();
    }

    /**
     * Tests expects behavior of {@link TransactionAttributeType#SUPPORTS}
     */
    @Test
    public void inheritsWhenMethodCalledFromParentWithChildClassLevelAnnotation() throws Exception {
        tm.begin();
        try {
            // active transaction
            childClass.bMethod();
        } finally {
            tm.rollback();
        }

        // no active transaction
        Assert.assertNull("There can't be active transaction here", tm.getTransaction());
        childClass.bMethod();
    }
}
