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

import java.io.IOException;

import org.apache.myfaces.orchestra.conversation.ConversationMessager;

/**
 * An interface that provides access to all the data necessary for Orchestra to work while isolating Orchestra
 * from the actual UI presentation framework being used.
 * <p>
 * A different concrete subclass of this type is then provided for each UI framework that Orchestra supports
 * (and additional subtypes can be defined by users if required). This allows Orchestra to support multiple
 * presentation frameworks, such as JSF and plain JSP. Method getCurrentInstance is used by Orchestra code
 * to locate the appropriate adapter instance when needed.
 * <p>
 * The method setCurrentInstance must be called at the start of each request in order to set the
 * appropriate adapter instance for whatever UI framework will be handing that request.
 */
public abstract class FrameworkAdapter
{
    private final static ThreadLocal instanceThreadLocal = new ThreadLocal();

    private ConversationMessager conversationMessager;
    private boolean prepared = false;

    /**
     * Expected to be called only by a servlet filter at the start and end of each request.
     * <p>
     * The prepare method of the provided frameworkAdapter is called if it has not already
     * been done. This ensures that the object is valid before the request begins. An
     * unchecked exception may therefore be thrown if the instance is misconfigured.
     */
    public static void setCurrentInstance(FrameworkAdapter frameworkAdapter)
    {
        if (frameworkAdapter == null)
        {
            instanceThreadLocal.remove();
            return;
        }
            
        synchronized(frameworkAdapter)
        {
            if (!frameworkAdapter.prepared)
            {
                frameworkAdapter.prepare();
            }
        }

        instanceThreadLocal.set(frameworkAdapter);
    }

    /**
     * Return an object that implements the non-static methods of this abstract
     * class in a manner appropriate for whatever UI framework is handling the
     * current request.
     */
    public static FrameworkAdapter getCurrentInstance()
    {
        return (FrameworkAdapter) instanceThreadLocal.get();
    }

    /**
     * Constructor.
     * <p>
     * This constructor deliberately takes no parameters, as this class may be extended
     * in future releases, adding new framework-specific properties if more are needed.
     * Changing the constructor would not be elegant, so instead this class uses 
     * "setter" methods to set the properties of this object, and the prepare() method
     * to ensure object integrity.
     */
    public FrameworkAdapter()
    {
    }

    /**
     * Ensure this object is valid, and perform any once-only initialisation required. 
     * <p>
     * This method must be called before any call to any method on this class
     * other than setters. Multiple calls to this method are safe; all except the first
     * one will be ignored. The setCurrentInstance method calls this method
     * automatically.
     * <p>
     * This method may be overridden by subclasses to perform once-only initialisation.
     * If this is done, call super.prepare at the end of the subclass implementation.
     * <p>
     * This method can throw unchecked exceptions if there is a problem with the
     * configuration of this object.
     */
    public void prepare()
    {
        if (conversationMessager == null)
        {
            // Not set via an explicit call to the setter, and not set
            // from a child class implementation of this method, so
            // try to do it here.
            conversationMessager = createConversationMessager();
        }

        synchronized(this)
        {
            prepared = true;
        }
    }

    /**
     * If this method is not overridden by a subclass, then method setConversationMessager
     * must be used to explicitly provide an instance.
     */
    protected ConversationMessager createConversationMessager()
    {
        throw new IllegalStateException("conversation messager configuration missing"); // NON-NLS
    }

    /**
     * Return an object that can report internal application problems to the user associated
     * with the current request.
     * <p>
     * If setConversationManager was called explicitly when this object was set up, then the
     * provided instance is returned. Otherwise what is returned is determined by the
     * concrete subclass. See the appropriate subclass documentation for details.
     */
    public ConversationMessager getConversationMessager()
    {
        return conversationMessager;
    }

    /**
     * Set the object that can report internal application problems to the user associated
     * with a request. This method is only ever expected to be called once, during
     * configuration of a FrameworkAdapter instance.
     */
    public void setConversationMessager(ConversationMessager conversationMessager)
    {
        this.conversationMessager = conversationMessager;
    }

    /**
     * Return the global init parameter with the specified name.
     * In most cases this is expected to return data from the ServletContext.
     */
    public abstract String getInitParameter(String key);

    /**
     * Get a value from the set of input parameters sent by the user as part
     * of the request.
     */
    public abstract Object getRequestParameterAttribute(String key);

    public abstract boolean containsRequestParameterAttribute(String key);

    /**
     * Get a request-scope variable.
     */
    public abstract Object getRequestAttribute(String key);

    public abstract void setRequestAttribute(String key, Object value);

    public abstract boolean containsRequestAttribute(String key);

    /**
     * Get a variable from the session-scope of the current user.
     */
    public abstract Object getSessionAttribute(String key);

    public abstract void setSessionAttribute(String key, Object value);

    public abstract boolean containsSessionAttribute(String key);

    /**
     * Instruct the remote browser to fetch the specified URL.
     */
    public abstract void redirect(String url) throws IOException;

    /**
     * Return the variable with the specified name, or null if no such bean exists.
     * <p>
     * In frameworks that support "managed beans", ie creation of objects on demand then
     * this may trigger the creation of the specified object. In frameworks that do not
     * support this, then the lookup may just return null if no object with the specified
     * name currently exists.
     * <p>
     * Note that no "property traversal" is required or expected; a name of "a.b.c"
     * is NOT evaluated as "property c of property b of bean a", but as the bean
     * with name 'a.b.c'.
     */
    public abstract Object getBean(String name);

    /**
     * Navigate to the specified logical destination.
     * <p>
     * For frameworks that have a built-in navigation system, that system should be
     * invoked.
     * <p>
     * For frameworks with no logical navigation system, the navigationName is treated
     * as a plain URL. Whether a FORWARD or a REDIRECT to this URL is perfomed is
     * determined by the subclass.
     */
    public abstract void invokeNavigation(String navigationName) throws IOException;

    /**
     * Return a string that identifies what view (logical application page) the user is
     * currently working with. 
     * <p>
     * The primary use of this is to pass it to the "view controller manager" to obtain
     * the "view controller" bean that holds the lifecycle methods needed for the current
     * view.
     * <p>
     * For frameworks like JSF, this is the "view id". 
     * <p>
     * By default a fixed value is returned, which allows frameworks that do not have
     * a concept of a "current logical page" to automatically support a single
     * "view controller" bean for the whole application.
     */
    public String getCurrentViewId()
    {
        return "defaultView";
    }
}
