package test.feature.aop;

import static org.junit.Assert.assertEquals;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.junit.Test;
import org.springframework.config.java.annotation.AutoBean;
import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.context.JavaConfigApplicationContext;
import org.springframework.config.java.plugin.aop.AspectJAutoProxy;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.Ordered;
import test.common.beans.TestBean;

public class MultipleProxiesAgainstSameTargetTests {

    /**
     * Baseline test demonstrating how the application of multiple aspects *should* work.
     * Respects Ordered interface semantics.
     */
    public @Test void testWithXML() {
        ClassPathXmlApplicationContext ctx =
            new ClassPathXmlApplicationContext(getClass().getSimpleName().concat(".xml"),
                                               getClass());
        TestBean foo = ((TestBean)ctx.getBean("foo"));
        String expectedName = "FOO_TWO_ONE";
        String actualName = foo.getName();

        // prove that both aspects were triggered, in the right order
        assertEquals(expectedName, actualName);


        // prove that only one proxy was created to handle both aspects' advice
        Aspect1 aspect1 = (Aspect1) ctx.getBean("aspect1");
        Aspect2 aspect2 = (Aspect2) ctx.getBean("aspect2");
        assertEquals(aspect1.proxyClass, aspect2.proxyClass);
    }


    /**
     * Should behave 1:1 with the above XML baseline test.
     */
    public @Test void testWithSjcAtAspectsStyle() {
        JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(ConfigWithAtAspects.class);

        TestBean foo = ((TestBean)ctx.getBean("foo"));
        String expectedName = "FOO_TWO_ONE";
        String actualName = foo.getName();

        // prove that both aspects were triggered, in the right order
        assertEquals(expectedName, actualName); // currently, actualName ends up being 'FOO_ONE' instead
    }

    @AspectJAutoProxy(proxyTargetClass=true)
    @Configuration
    abstract static class ConfigWithAtAspects {
        public @Bean TestBean foo() {
            return new TestBean("FOO");
        }
        
        public abstract @AutoBean Aspect1 aspect1();
        public abstract @AutoBean Aspect2 aspect2();
    }

    @Aspect
    static class Aspect1 implements Ordered {
       Class<? extends TestBean> proxyClass;

       @Before("execution(* getName()) && this(proxy) && target(target)")
       public void getName(TestBean proxy, TestBean target) {
           target.setName(target.getName().concat("_ONE"));
           this.proxyClass = proxy.getClass();
       }

       public int getOrder() {
           return 2;
       }
    }

    @Aspect
    static class Aspect2 implements Ordered {
       Class<? extends TestBean> proxyClass;

       @Before("execution(* getName()) && this(proxy) && target(target)")
       public void getName(TestBean proxy, TestBean target) {
           target.setName(target.getName().concat("_TWO"));
           this.proxyClass = proxy.getClass();
       }

       public int getOrder() {
           return 1;
       }
    }

    // -------------------------------------------------------------------------

    public @Test void testWithSjcInlineAspectStyle() {
        JavaConfigApplicationContext ctx =
            new JavaConfigApplicationContext(ConfigWithMultipleInlineAspects.class);

        // by default the order of inline advice methods is based on the order in
        // which the methods show up in the class.
        TestBean foo = ((TestBean)ctx.getBean("foo"));
        String expectedName = "FOO_Z_A";
        String actualName = foo.getName();

        // prove that both aspects were triggered, in the right order
        assertEquals(expectedName, actualName);

        // prove that only one proxy was created to handle both advice methods
        ConfigWithMultipleInlineAspects aspect = ctx.getBean(ConfigWithMultipleInlineAspects.class);
        assertEquals(aspect.proxyClass1, aspect.proxyClass2);
    }

    @AspectJAutoProxy(proxyTargetClass=true)
    @Aspect
    @Configuration
    static class ConfigWithMultipleInlineAspects {
        Class<? extends TestBean> proxyClass1;
        Class<? extends TestBean> proxyClass2;

        public @Bean TestBean foo() {
            return new TestBean("FOO");
        }

       @Before("execution(* getName()) && this(proxy) && target(target)")
       public void zAdvice(TestBean proxy, TestBean target) {
           target.setName(target.getName().concat("_Z"));
           this.proxyClass2 = proxy.getClass();
       }
       
       @Before("execution(* getName()) && this(proxy) && target(target)")
       public void aAdvice(TestBean proxy, TestBean target) {
           target.setName(target.getName().concat("_A"));
           this.proxyClass1 = proxy.getClass();
       }

    }

}
