/*
 * 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.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import java.io.Serializable;

/**
 * An advice which is added to all conversation scoped beans.
 * 
 * <p>It does the following:
 * <ul>
 * <li>Maintain the {@link Conversation#getCurrentInstance()}</li>
 * <li>End the conversation if it has been invalidated</li>
 * </ul>
 * </p>
 * 
 * <p>A bean that is declared as belonging to a particular conversation is
 * always wrapped by the IOC system in a proxy object. When any method is
 * called on that proxy, the call is forwarded to the "invoke" method here.
 * This class then ensures that the "current conversation" is set to the
 * conversation configured for that target bean. The result is that the real
 * bean can call Conversation.getCurrentInstance() and always receives a
 * reference to the conversation object that has been configured for it.</p>
 * 
 * <p>In addition, on return from the target bean method, this object
 * checks whether the conversation has been marked as invalid. If so (and
 * the conversation is not in use by something further up the call stack)
 * then destroy the conversation object.</p>
 * 
 * <p>Attempting to invoke a methods on a bean that is associated with a
 * conversation that has been destroyed will cause an IllegalStateException
 * to be thrown. This is a safety-net to detect cases where a reference to a
 * proxy object bound to a specific conversation has been kept for longer than the
 * conversation lifetime. This is bad, as it (a) attempts to access a "stale"
 * conversation, and (b) prevents the destroyed conversation from being
 * garbage-collected (although all the beans in it will already have been
 * removed).</p> 
 */
public class CurrentConversationAdvice implements MethodInterceptor, Serializable
{
    private final CurrentConversationInfo conversationInfo;

    public CurrentConversationAdvice(Conversation conversation, String beanName)
    {
        conversationInfo = new CurrentConversationInfo(conversation, beanName);
    }

    public Object invoke(MethodInvocation methodInvocation) throws Throwable
    {
        // Save the current conversation so it can be restored later.
        // Note that for "top-level" calls, the saved value is null.
        CurrentConversationInfo previous = Conversation.getCurrentInstanceInfo();

        // Set the appropriate conversation for the target object. 
        Conversation.setCurrentInstance(conversationInfo);

        try
        {
            // Note: Conversation.enterConversation throws IllegalStateException
            // when the conversation has been destroyed (invalidated).
            Conversation conversation = conversationInfo.getConversation();
            conversation.enterConversation();

            return methodInvocation.proceed();
        }
        finally
        {
            // Always restore the previous conversation (which may be null).
            // Do this before anything else in case other methods throw exceptions.
            Conversation.setCurrentInstance(previous);

            conversationInfo.getConversation().leaveConversation();

            if (conversationInfo.getConversation().isQueueInvalid())
            {
                conversationInfo.getConversation().invalidate();
            }
        }
    }
}
