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

import java.io.IOException;

/**
 * Some helpers usable for public use
 */
public final class ConversationUtils
{
    private ConversationUtils()
    {
    }

    /**
     * End and restart the given conversation.
     * <p>
     * This method tries to return a bean whose name is the same as the
     * name of the specified conversation. If no such bean is defined, then
     * null is returned.
     * <p>
     * Note that the return value is quite different from the
     * {@link Conversation#invalidateAndRestart()} method, which returns
     * an instance of the most recently invoked conversation-scoped bean.
     */
    public static Object invalidateAndRestart(Conversation conversation)
    {
        String name = conversation.getName();

        conversation.invalidateAndRestart();

        return FrameworkAdapter.getCurrentInstance().getBean(name);
    }

    /**
     * Return a reference to the most recently-invoked bean that is declared as being in
     * a conversation scope.
     * <p>
     * When using an interceptor-based AOP framework, a bean that passes "this" to another
     * object is bypassing any aspects. Any "callbacks" invoked via that reference
     * will not apply the aspects that Orchestra has configured. This is particularly
     * nasty when using Orchestra's persistence support as Orchestra uses an aspect to
     * configure the correct "persistence context" for a bean when it is invoked.
     * <p>
     * Therefore, when a bean wishes to pass a reference to itself elsewhere then it should
     * use this method rather than passing "this" directly. It is acknowledged that this is
     * less than ideal as it does couple code to Orchestra.
     * <p>
     * In most cases it is better to call <code>ConversationUtils.bindToCurrent(this)</code>.
     * That code works regardless of whether the caller is configured in the dependency-injection
     * framework as a conversation-scoped bean or not, ie it makes the code independent of the
     * configuration which is always a good idea.
     *  
     * @since 1.1
     */
    
    /*
     * An alternative to this is to use AOP "load-time-weaving", where a custom classloader
     * uses a configuration file to apply the necessary interceptors directly to the class
     * rather than using the scope manager (eg AbstractSpringOrchestraScope) to define the
     * aspects as interceptors. In this case, the "this" reference is an object that has
     * the interceptors attached so the problem does not occur. But the classes which are
     * to be modified on class-load-time are determined by the orchestra configuration
     * files which specify what beans are conversation-scoped. In the worst case, this
     * information is actually held in annotations on the beans themselves, which means
     * that the class is loaded before the information on how to weave it exists. The only
     * solution here appears to be to instead weave every possible class that might be
     * conversation-scoped (eg all those with @Scope annotation, or all those that are in
     * a certain package). On object creation the aspect performs a "lookup" to find its
     * conversation, and if none then does nothing.
     */
    public static Object getCurrentBean()
    {
        CurrentConversationInfo currentConversationInfo = Conversation.getCurrentInstanceInfo();
        if (currentConversationInfo == null)
        {
            return null;
        }

        String name = currentConversationInfo.getBeanName();

        return FrameworkAdapter.getCurrentInstance().getBean(name);
    }

    /**
     * End and restart the current conversation.
     * <p>
     * The "current conversation" is the conversation associated with the
     * most-recently-invoked conversation-scoped bean.
     * <p>
     * The returned object is a new instance of the most-recently-invoked
     * conversation-scope bean.
     * <p>
     * This method is generally expected to be called from a conversation-scoped
     * bean, in which case the conversation that is invalidated is the one in
     * which the calling bean instance lives, and the returned object is the
     * instance that will replace the calling object. 
     */
    public static Object invalidateAndRestartCurrent()
    {
        CurrentConversationInfo currentConversationInfo = Conversation.getCurrentInstanceInfo();
        String name = currentConversationInfo.getBeanName();

        currentConversationInfo.getConversation().invalidateAndRestart();

        return FrameworkAdapter.getCurrentInstance().getBean(name);
    }

    /**
     * If no conversation with name <code>conversationName</code> is active a redirect to
     * <code>redirectViewId</code> will be issued.
     * <p>
     * If <code>redirectViewId</code> starts with an slash ('/') the context path will be added.
     */
    public static void ensureConversationRedirect(String conversationName, String redirectViewId)
    {
        if (!ConversationManager.getInstance().hasConversation(conversationName))
        {
            try
            {
                FrameworkAdapter.getCurrentInstance().redirect(redirectViewId);
            }
            catch (IOException e)
            {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * Invalidates a conversation if it exists.
     */
    public static void invalidateIfExists(String name)
    {
        Conversation conversation = ConversationManager.getInstance().getConversation(name);
        if (conversation != null)
        {
            conversation.invalidate();
        }
    }

    /**
     * Returns a proxy object that "binds" the specified instance to the current conversation.
     * <p>
     * Invoking the returned proxy object will set up the current conversation before passing
     * the method call on to the provided instance, ie calls to the instance then run in the
     * same conversation as the code making a call to this method.
     * <p>
     * This is simply a shorcut for "Conversation.getCurrentInstance().bind(instance);".
     * 
     * @since 1.3
     */
    public static Object bindToCurrent(Object instance)
    {
        return Conversation.getCurrentInstance().bind(instance);
    }
}
