/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.myfaces.orchestra.conversation;

import org.apache.myfaces.orchestra.conversation.basic.LogConversationMessager;
import org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter;
import org.apache.myfaces.orchestra.frameworkAdapter.local.LocalFrameworkAdapter;
import org.springframework.aop.scope.ScopedObject;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;

/**
 * Test various aspects of the conversation handling, particularly as they interact
 * with Spring's AOP proxying of various forms.
 * <p>
 * Spring can generate aop proxies in two ways: with CGLIB and java.lang.reflect.Proxy.
 * <p>
 * The CGLIB implementation can proxy concrete classes, by creating a new class
 * instance that subclasses the original (but whose methods all simply forward to
 * the attached advices, which then forward to the real target object). These CGLIB
 * proxy classes are not final, ie they can themselves be proxied if necessary
 * (though that is inefficient).
 * <p>
 * The java.lang implementation can only proxy interfaces; it creates a concrete
 * final class that implements those interfaces, where the method implementations
 * again forward to the attached handlers that then forward to a real target object.
 * Because these generated classes are final, they cannot be subclassed.
 * <p>
 * Spring doesn't really care about proxies; what it cares about is being able to
 * attach a set of Advice objects to a bean. But in practice, if Advices are to
 * be attached, then there must be a proxy object to attach them to (unless
 * load-time-weaving is supported). Note that "introductory advices" can also be
 * defined, which dynamically add interfaces to the target bean.
 * <p>
 * Of course Spring doesn't hard-wire advices to beans. Instead when a bean
 * instance is created, the set of registered BeanPostProcessors are run. Some
 * of those may then choose to attach advices; for example Spring's declarative
 * transaction-handling support uses a proxy. Any post-processor that wants to
 * create a proxy should really subclass AbstractAutoProxyCreator. This class
 * handles caching of proxy classes for beans. It also handles the case where
 * multiple BeanPostProcessors want to proxy the same bean, by merging the
 * advices together so that one single proxy instance is needed.
 * <p>
 * The BeanNameAutoProxyCreator is an interesting BeanPostProcessor that triggers
 * when creating beans with specific names; it simply looks for all registered beans
 * of type Advisor, asks each whether they want to apply to a particular bean, and
 * if so then asks it for an Advice instance to use. Of course the presence of
 * an Advice then causes the ancestor AbstractAutoProxyCreator to create a proxy
 * for the target bean.
 * <p>
 * When a proxy is created, there is some complex logic to determine whether the
 * jdk or cglib library is used. If the target bean has no interfaces, then
 * cglib is of course used. If the bean has been explicitly marked as "use cglib"
 * then cglib is used. Otherwise java.lang.reflect.Proxy is used.
 * <p>
 * Orchestra needs to attach its own advices to orchestra beans, in order to
 * implement its persistence support. 
 * <p>
 * There is a standard "scoped proxying" feature that can be applied to a bean by
 * adding aop:scoped-proxy as an element inside a bean definition. This is somewhat
 * different from the above. This proxy always subclasses the real original class
 * of the bean (ie does not include any interfaces added via "introductory advices").
 * However as a side-effect, the presence of this tag deliberately forces the target
 * bean to be created with CGLIB (not sure why this is done). A "real" bean can 
 * therefore effectively have two proxies: a "scoping" one, and an "advising" one.
 * Orchestra generally recommends that aop:scoped-proxy be avoided, and instead
 * implements its own equivalent. However when an aop:scoped-proxy *is* defined
 * for an orchestra-scoped bean, then it detects this and skips the creation of
 * its own equivalent proxy object.
 * <p>
 * This test case tries to test that Orchestra works correctly when in the presence
 * of all these various proxying options.
 */
public class TestScope extends AbstractDependencyInjectionSpringContextTests
{
    protected String[] getConfigLocations()
    {
        return new String[]
            {
                "classpath:org/apache/myfaces/orchestra/conversation/TestScope.xml"
            };
    }

    protected void onSetUp() throws Exception
    {
        super.onSetUp();
    }
    
    public void testFoo() throws Exception {

        // Set up the FrameworkAdapter. This is needed by any ConversationManager operation.
        LocalFrameworkAdapter frameworkAdapter = new LocalFrameworkAdapter();
        frameworkAdapter.setApplicationContext(applicationContext);
        frameworkAdapter.setConversationMessager(new LogConversationMessager());
        FrameworkAdapter.setCurrentInstance(frameworkAdapter);

        // Get the object from spring. Orchestra should wrap it in a proxy that implements ScopedObject 
        SimpleBean b1 = (SimpleBean) applicationContext.getBean("unscopedBean");
        assertNotNull(b1);
        assertTrue(b1 instanceof ScopedObject);

        // The proxy also checks any return values, and modifies them so that methods on the target
        // that return the target object actually return the proxy. Tricky! This means that the
        // "method chaining" pattern can work, eg foo.doX().doY().doZ() and all invocations pass
        // through the proxy.
        SimpleBean b1a = b1.getThis();
        assertTrue(b1 == b1a);
        assertTrue(b1 == b1.getThisRef());
        
        // However the proxy cannot completely hide itself. The most obvious way is that the proxy
        // has fields (because it subclasses SimpleBean) but they are not initialised by the call
        // to getThis(), because it is the target object that ran that method, not the proxy.
        assertNull(b1.thisRef);
        assertNull(b1.thisRefHolder);
        
        // And it cannot perform its "ref replacement" trick when the ref is nested inside some
        // more complicated object. Note that this means that when the target object passes its
        // "this" parameter to another object, it is the raw unproxied this that gets passed.
        SimpleBean[] refHolder = b1.getThisRefHolder();
        assertNotNull(refHolder);
        assertFalse(b1 == refHolder[0]);
        
        b1.setData("hello, world");
        String s1 = b1.getData();
        assertEquals("hello, world", s1);

        assertEquals(1, b1.getConversationAwareCount());
    }
    
    public void testCorrectConversation() throws Exception
    {
        // Set up the FrameworkAdapter. This is needed by any ConversationManager operation.
        LocalFrameworkAdapter frameworkAdapter = new LocalFrameworkAdapter();
        frameworkAdapter.setApplicationContext(applicationContext);
        frameworkAdapter.setConversationMessager(new LogConversationMessager());
        FrameworkAdapter.setCurrentInstance(frameworkAdapter);

        final SimpleBean b1 = (SimpleBean) applicationContext.getBean("unscopedBean");
        assertNotNull(b1);

        // We are not within any method of an orchestra bean, so no current bean exists
        Object currentBean = ConversationUtils.getCurrentBean();
        assertNull(currentBean);

        b1.callback(new Runnable() {
            public void run()
            {
                // We are within a method of the orchestra bean b1, so it should be the 
                // current bean.
                Object currentBean = ConversationUtils.getCurrentBean();
                assertTrue(b1 == currentBean);
            }
        });
    }
    
    public void testPlainBean() throws Exception
    {
        // Set up the FrameworkAdapter. This is needed by any ConversationManager operation.
        LocalFrameworkAdapter frameworkAdapter = new LocalFrameworkAdapter();
        frameworkAdapter.setApplicationContext(applicationContext);
        frameworkAdapter.setConversationMessager(new LogConversationMessager());
        FrameworkAdapter.setCurrentInstance(frameworkAdapter);

        // The object is proxied by a JDK proxy; this is the default behaviour. It therefore
        // has all the interfaces of a SimpleBean, and is backed by a SimpleBean, but cannot
        // be cast to one.
        final SimpleInterface b1 = (SimpleInterface) applicationContext.getBean("plainBean");
        assertNotNull(b1);
        assertFalse(b1 instanceof SimpleBean);
        b1.doSomething();
    }
}
