/*
 * 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.spring;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.myfaces.orchestra.conversation.Conversation;

/**
 * Maintain the appropriate persistence state for the current call-stack.
 * <p>
 * This class is a MethodInterceptor (an AOP Advice) which should be configured to intercept
 * calls to all conversation-scoped beans. It ensures that the appropriate PersistenceContext
 * object for this conversation is placed into the default location, by invoking the
 * <i>bind</i> method on the persistence context object. Typically the bind method stores
 * the persistence context into a thread-local variable, but that is implementation-specific.
 * <p>
 * When Spring code retrieves the persistence context in order to inject it into a bean,
 * it then finds the correct context for the conversation that is associated with the
 * nearest conversation-scoped bean in the callstack.    
 * <p>
 * If no PersistenceContext yet exists for the conversation associated with the bean
 * that is being invoked then one is created using {@link PersistenceContextFactory}.
 * <p>
 * A reference to the {@link PersistenceContext} is put into the conversation attribute
 * map wrapped in an {@link PersistenceContextCloser} so that when the conversation
 * ends a callback occurs on it which closes the underlying object.
 */
public class PersistenceContextConversationInterceptor implements MethodInterceptor
{
    private final static String PERSISTENCE_CONTEXT_CONV_ATTRIBUTE =
        PersistenceContextConversationInterceptor.class.getName() + ".PERSISTENCE_CONTEXT";
    public final static String REQUEST_ATTRIBUTE =
        PersistenceContextConversationInterceptor.class.getName() + ".USED_PERSISTENCE_CONTEXTS";

    private PersistenceContextFactory persistenceContextFactory;

    public PersistenceContextConversationInterceptor()
    {
    }

    public void setPersistenceContextFactory(PersistenceContextFactory persistenceContextFactory)
    {
        this.persistenceContextFactory = persistenceContextFactory;
    }

    public Object invoke(MethodInvocation methodInvocation) throws Throwable
    {
        PersistenceContext persistenceContext = null;

        // The CurrentConversationAdvice object is expected to have already been executed,
        // so the "current conversation" is now the one associated with the bean being
        // invoked. But the persistence context currently configured is still unchanged,
        // so here we set up the context associated with this conversation (creating
        // it if this is the first call to this conversation).
        Conversation conversation = Conversation.getCurrentInstance();
        if (conversation != null)
        {
            PersistenceContextCloser persistenceContextCloser = (PersistenceContextCloser)
                conversation.getAttribute(PERSISTENCE_CONTEXT_CONV_ATTRIBUTE);
            if (persistenceContextCloser != null)
            {
                // This is not the first call to this conversation (the closer exists). So
                // retrieve the existing PersistenceContext for this conversation from the
                // closer object.
                persistenceContext = persistenceContextCloser.getPersistenceContext();
            }

            if (persistenceContext == null)
            {
                // This must be the first time any bean in this conversation has been
                // invoked. Therefore create a PersistenceContext. Also create a
                // PersistenceContextCloser and cache it in the conversation.
                persistenceContext = persistenceContextFactory.create();

                conversation.setAttribute(
                    PERSISTENCE_CONTEXT_CONV_ATTRIBUTE,
                    new PersistenceContextCloser(persistenceContext));
            }
        }

        if (persistenceContext != null)
        {
            // Make the persistenceContext object the "current" one. All PersistenceContext
            // instances that Spring injected are proxies that look up the actual target
            // PersistenceContext on each method call; after this bind call, the object
            // that they will retrieve is our new persistenceContext.
            persistenceContext.bind();
        }

        try
        {
            return methodInvocation.proceed();
        }
        finally
        {
            if (persistenceContext != null)
            {
                persistenceContext.unbind();
            }
        }
    }

    /*
    protected void registerPersistenceContextUsage(PersistenceContext persistenceContext)
    {
        FrameworkAdapterInterface fai = FrameworkAdapter.getInstance();
        Set persistencesContexts = (Set) fai.getRequestAttribute(REQUEST_ATTRIBUTE);
        if (persistencesContexts == null)
        {
            persistencesContexts = new HashSet();
            fai.setRequestAttribute(REQUEST_ATTRIBUTE, persistencesContexts);
        }
        if (!persistencesContexts.contains(persistenceContext))
        {
            persistencesContexts.add(persistenceContext);
        }
    }

    public static void cleanupPersistence()
    {
        FrameworkAdapterInterface fai = FrameworkAdapter.getInstance();
        Set persistencesContexts = (Set) fai.getRequestAttribute(REQUEST_ATTRIBUTE);
        if (persistencesContexts == null)
        {
            return;
        }

        Iterator iterPersistencesContexts = persistencesContexts.iterator();
        while (iterPersistencesContexts.hasNext())
        {
            PersistenceContext persistenceContext = (PersistenceContext) iterPersistencesContexts.next();
            persistenceContext.unbind();
        }
    }
    */
}
