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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.orchestra.conversation.ConversationMessager;
import org.apache.myfaces.orchestra.conversation.basic.LogConversationMessager;
import org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter;
import org.apache.myfaces.orchestra.lib._ClassUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * An implementation of the FrameworkAdapter for plain servlet environments.
 * <p>
 * This class requires the BasicFrameworkAdapterFilter to be configured to run
 * for every request.
 * 
 * <h2>Configuring the ConversationMessager</h2>
 * 
 * This class allows the concrete ConversationMessager instance returned
 * by the getConversationMessager method to be configured
 * in a number of ways:
 * <ul>
 * <li>By calling an explicit setter, passing an instance.</li>
 * <li>By specifying a conversationMessagerName parameter to the constructor
 * of this method which matches a bean name defined in the dependency injection
 * framework.</li>
 * <li>By specifying a conversationMessagerName that is the fully-qualified
 * name of a ConversationMessager class to be instantiated</li>
 * <li>Otherwise, method createDefaultConversationMessager is called which
 * returns a LogConversationMessager instance (unless overridden in a subclass).</li>
 * </ul>
 * 
 * See also the BasicFrameworkAdapterFilter class.
 */
public class BasicFrameworkAdapter extends FrameworkAdapter
{
    private final static String ISE_MESSAGE="No request/response data available"; // NON-NLS

    private final static ThreadLocal httpServletRequestThreadLocal = new ThreadLocal();
    private final static ThreadLocal httpServletResponseThreadLocal = new ThreadLocal();

    private final Log log = LogFactory.getLog(BasicFrameworkAdapter.class);

    private final ServletContext servletContext;
    private final String conversationMessagerClass;

    public BasicFrameworkAdapter(ServletContext context, String conversationMessagerClass)
    {
        servletContext = context;
        this.conversationMessagerClass = conversationMessagerClass;
    }

    protected ConversationMessager createConversationMessager()
    {
        if (conversationMessagerClass == null)
        {
            return createDefaultConversationMessager();
        }

        // First try to find a bean of that name in the dependency-injection
        // framework.
        Object instance = getBean(conversationMessagerClass);
        if (instance instanceof ConversationMessager)
        {
            return (ConversationMessager) instance;
        }

        // Now resort to trying the value as a classname.
        return (ConversationMessager) _ClassUtils.newInstance(conversationMessagerClass);
    }

    protected ConversationMessager createDefaultConversationMessager()
    {
        return new LogConversationMessager();
    }

    private HttpServletRequest getRequest()
    {
        return (HttpServletRequest) httpServletRequestThreadLocal.get();
    }

    private HttpServletResponse getResponse()
    {
        return (HttpServletResponse) httpServletResponseThreadLocal.get();
    }

    public void beginRequest(ServletRequest req, ServletResponse rsp)
    {
        log.debug("Beginning request");
        if (req instanceof HttpServletRequest)
        {
            httpServletRequestThreadLocal.set(req);
        }
        if (rsp instanceof HttpServletResponse)
        {
            httpServletResponseThreadLocal.set(rsp);
        }

        FrameworkAdapter.setCurrentInstance(this);
    }

    public void endRequest()
    {
        log.debug("Ending request");
        FrameworkAdapter.setCurrentInstance(null);
        httpServletRequestThreadLocal.set(null);
        httpServletResponseThreadLocal.set(null);
    }

    public String getInitParameter(String key)
    {
        return servletContext.getInitParameter(key);
    }

    public Object getRequestParameterAttribute(String key)
    {
        HttpServletRequest request = getRequest();
        if (request != null)
        {
            return request.getParameter(key);
        }

        throw new IllegalStateException(ISE_MESSAGE);
    }

    public boolean containsRequestParameterAttribute(String key)
    {
        HttpServletRequest request = getRequest();
        if (request != null)
        {
            return request.getParameter(key) != null;
        }

        throw new IllegalStateException(ISE_MESSAGE);
    }

    public Object getRequestAttribute(String key)
    {
        HttpServletRequest request = getRequest();
        if (request != null)
        {
            return request.getAttribute(key);
        }

        throw new IllegalStateException(ISE_MESSAGE);
    }

    public void setRequestAttribute(String key, Object value)
    {
        HttpServletRequest request = getRequest();
        if (request != null)
        {
            request.setAttribute(key, value);
            return;
        }

        throw new IllegalStateException(ISE_MESSAGE);
    }

    public boolean containsRequestAttribute(String key)
    {
        HttpServletRequest request = getRequest();
        if (request != null)
        {
            return request.getAttribute(key) != null;
        }

        throw new IllegalStateException(ISE_MESSAGE);
    }

    public Object getSessionAttribute(String key)
    {
        HttpServletRequest request = getRequest();
        if (request != null && request.getSession(true) != null)
        {
            return request.getSession(true).getAttribute(key);
        }

        throw new IllegalStateException(ISE_MESSAGE);
    }

    public void setSessionAttribute(String key, Object value)
    {
        HttpServletRequest request = getRequest();
        if (request != null && request.getSession(true) != null)
        {
            request.getSession(true).setAttribute(key, value);
            return;
        }

    }

    public boolean containsSessionAttribute(String key)
    {
        HttpServletRequest request = getRequest();
        if (request != null && request.getSession(true) != null)
        {
            return request.getSession(true).getAttribute(key) != null;
        }

        throw new IllegalStateException(ISE_MESSAGE);
    }

    protected String getRequestContextPath()
    {
        HttpServletRequest request = getRequest();
        if (request != null)
        {
            return request.getContextPath();
        }

        throw new IllegalStateException(ISE_MESSAGE);
    }

    public void redirect(String url) throws IOException
    {
        StringBuffer redir = new StringBuffer();
        if (url.startsWith("/"))
        {
            redir.append(getRequestContextPath());
        }
        redir.append(url);

        HttpServletResponse rsp = getResponse();
        String dstUrl = rsp.encodeRedirectURL(redir.toString());
        rsp.sendRedirect(dstUrl);
    }

    /**
     * Look in the request and session scopes for an entry
     * with the specified name.
     * <p>
     * This basic adapter class does not support invoking the JSP expression
     * evaluator; no "variable resolver" will ever be used to look up the
     * specified name.
     * <p>
     * TODO: also look in the application scope.
     * <p>
     * TODO: investigate invoking the jsp.ExpressionFactory class to look up
     * the variable. Possibly that could be done in a different JspFrameworkAdapter
     * class.
     */
    public Object getBean(String name)
    {
        Object obj;

        obj = getRequestAttribute(name);
        if (obj != null)
        {
            return obj;
        }

        obj = getSessionAttribute(name);
        if (obj != null)
        {
            return obj;
        }

        // TODO: look up application-scoped objects.

        return null;
    }

    /**
     * Perform a redirect to the specified url.
     * <p>
     * A redirect is done rather than a forward so that the remote browser has its
     * current url updated appropriately. Note that a redirect does cause any
     * request-scoped variables to be discarded.
     */
    public void invokeNavigation(String navigationName) throws IOException
    {
        redirect(navigationName);
    }
}
