/*
 * Copyright 2002-2008 the original author or authors.
 *
 * 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 test.feature.aop;

import static org.hamcrest.CoreMatchers.equalTo;

import static org.junit.Assert.assertThat;

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

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import org.junit.Test;

import org.springframework.beans.factory.BeanCreationException;

import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.context.ConfigurableJavaConfigApplicationContext;
import org.springframework.config.java.context.JavaConfigApplicationContext;
import org.springframework.config.java.plugin.aop.AspectJAutoProxy;

import test.common.beans.ITestBean;
import test.common.beans.TestBean;


/**
 * This test showcases the most up-to-date techniques for using aspects within Spring JavaConfig.
 * Currently, the programming model is a bit awkward, perhaps a lot awkward. See the method comments
 * below for details. We'll want to consider changing this before 1.0 GA.
 *
 * TODO: JAVADOC - out of date, eliminate?
 *
 * @author  Chris Beams
 */
public class AspectTests {
    @Test(expected = BeanCreationException.class)
    public void testInvalidAspect() { new JavaConfigApplicationContext(InvalidAspectConfig.class); }

    /**
     * Here we're going to create a new AppCtx against the {@link ConfigWithAspects} configuration,
     * and prove that our advice gets applied properly.
     */
    @Test
    public void testApplicationOfSimpleAspect() {
        ConfigurableJavaConfigApplicationContext ctx = new JavaConfigApplicationContext(ConfigWithAspects.class);

        ITestBean foo = ctx.getBean(ITestBean.class, "foo");
        assertThat(foo.getName(), equalTo("foo"));

        ConfigWithAspects configBean = ctx.getBean(ConfigWithAspects.class);
        assertThat(configBean.beforeCount, equalTo(1));
        assertThat(configBean.afterCount, equalTo(1));

        foo.getName();

        assertThat(configBean.beforeCount, equalTo(2));
        assertThat(configBean.afterCount, equalTo(2));
    }

    /**
     * This test is all about demonstrating how to define a standalone, reusable aspect and then use
     * it within a given Configuration. Trouble is, that we'll see that our 'standalone aspect' also
     * has to be a Configuration.
     */
    @Test
    public void testAspectModularity() {
        // instantiate a context against our AppConfig configuration. Remember
        // that AppConfig uses the @Import annotation to pull in our
        // 'standalone aspect' PropertyChangeTracker. Notice how
        // PropertyChangeTracker is a Configuration? Doesn't that seem strange?
        ConfigurableJavaConfigApplicationContext ctx;
        ctx = new JavaConfigApplicationContext(AppConfig.class);

        // grab out the aspect/configuration bean from the context, we'll
        // introspect it to see if the advice method executed in a moment
        PropertyChangeTracker changeTracker = ctx.getBean(PropertyChangeTracker.class);

        // before any calls to our service object's setCacheSize method, our
        // config beans' respective propertyChangeCount values should be at zero
        assertThat(changeTracker.propertyChangeCount, equalTo(0));

        // get our service object, and change a property on it.
        Service service = ctx.getBean(Service.class);
        service.setCacheSize(2500);

        // if our aspects were applied properly, the propertyChangeCount values
        // should now be incremented by one because we've changed a property
        // exactly once.
        assertThat(changeTracker.propertyChangeCount, equalTo(1));
    }

    @AspectJAutoProxy
    @Aspect
    @Configuration
    public static class InvalidAspectConfig {
        // invalid -> declares no advice methods
        @Before("something()")
        @Bean
        public TestBean alice() { return new TestBean(); }
    }

    @AspectJAutoProxy
    @Aspect
    @Configuration
    static class ConfigWithAspects {
        Log log = LogFactory.getLog(ConfigWithAspects.class);
        int beforeCount = 0;
        int afterCount = 0;

        @Before("getName(testBean)")
        public void logGetNameCall(TestBean testBean) {
            log.info("about to call getName on " + testBean);
            beforeCount++;
        }

        @After("getName(testBean)")
        public void logGetNameCalled(TestBean testBean) {
            log.info("just called getName on " + testBean);
            afterCount++;
        }

        @Pointcut("execution(* *..TestBean.getName(..)) && target(testBean)")
        public void getName(TestBean testBean) { }

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

    @AspectJAutoProxy
    @Configuration
    static class AppConfig {
        Log log = LogFactory.getLog(AppConfig.class);

        int propertyChangeCount = 0;

        @Bean
        public Service service() { return new Service(); }
        
        public @Bean PropertyChangeTracker propertyChangeTracker() {
            return new PropertyChangeTracker();
        }
    }

    @Aspect
    static class PropertyChangeTracker {
        Log log = LogFactory.getLog(PropertyChangeTracker.class);

        public int propertyChangeCount = 0;

        @Before("execution(* set*(*))")
        public void trackChange() {
            log.info("property just changed...");
            propertyChangeCount++;
        }
    }

    static class Service {
        private int cacheSize = 100;

        public void setCacheSize(int cacheSize) { this.cacheSize = cacheSize; }

        public int getCacheSize() { return cacheSize; }
    }

}
