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

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import javax.faces.FacesException;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.portlet.ActionResponse;
import javax.portlet.PortletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter;
import org.apache.myfaces.orchestra.requestParameterProvider.RequestParameterServletFilter;

/**
 * Class used by OrchestraFacesContextFactory to allow orchestra work in portlets
 * 
 * In portlet world we have the following differences against servlets:
 * 
 * 1. In servlet, the same thread is responsible from execute action and render. But
 *    in portlets we could have separate threads that execute action and render.
 * 2. The request attribute values between portlet action and render could be lost, so
 *    if we need some param to be passed between action and render phase. 
 * 
 * 
 * @author Leonardo Uribe(latest modification by $Author: lu4242 $)
 * @version $Revision: 826577 $ $Date: 2009-10-18 20:50:13 -0500 (Sun, 18 Oct 2009) $
 */
public class PortletOrchestraFacesContextFactory
{
    private final Log log = LogFactory.getLog(PortletOrchestraFacesContextFactory.class);
    
    private final AtomicLong _count;
    
    private final static String REQUEST_CONTEXT_PARAM = "requestContext";
    
    private final static String CONVERSATION_CONTEXT_PARAM = "conversationContext";
    
    public final static String REQUEST_HANDLERS = "org.apache.myfaces.orchestra.REQUEST_HANDLERS";
    
    /**
     * Constant used by orchestra to check if we are on a portlet request or not.
     */
    public final static String PORTLET_LIFECYCLE_PHASE = "javax.portlet.orchestra.phase";


    public PortletOrchestraFacesContextFactory()
    {
        this._count = new AtomicLong(1);
    }
    
    /**
     * Return the next token to identify in a unique way the current request. This is 
     * used when there is no conversationContext param available. In that remote case,
     * a dummy param is added just to allow pass installed handlers between action
     * and request scope.
     *  
     * @return
     */
    protected String _getNextToken()
    {
        long nextToken = _count.incrementAndGet();
        return Integer.toString(hashCode())+nextToken;
    }
    
    public FacesContext getFacesContext(final FacesContext facesContext, Object context, 
            Object request, Object response) throws FacesException
    {
        ExternalContext externalContext = facesContext.getExternalContext();
        List handlers = null;
        boolean init = false;
        String nextToken = null;
        
        Map requestMap = externalContext.getRequestMap();
        
        RequestType type = ExternalContextUtils.getRequestType(context, request);
        
        if (RequestType.RENDER.equals(type))
        {
            PortletRequest portletRequest = (PortletRequest) request;
            nextToken = portletRequest.getParameter(CONVERSATION_CONTEXT_PARAM);            
            if (nextToken == null)
            {
                nextToken = portletRequest.getParameter(REQUEST_CONTEXT_PARAM);
            }
            if (nextToken != null)
            {
                //Restore it from application map and remove it from application map,
                //since it will not be used anymore
                handlers = (List) externalContext.getApplicationMap().remove(REQUEST_HANDLERS+nextToken);
            }
        }
        
        // AbstractSpringOrchestraScope add retrieved values to request scope, but in portlet
        // containers later this map could be serialized. This cause orchestra try to resolve
        // its proxy, but since FrameworkAdapter instance is only available after this point.
        // it causes an Exception.
        // Since from AbstractSpringOrchestraScope we can't have access to ExternalContext, we should put
        // a param to identify if we are or not in portlet request and which phase we are, in a similar
        // way as JSR-301 does 
        if (RequestType.RENDER.equals(type))
        {
            requestMap.put(PORTLET_LIFECYCLE_PHASE, "RENDER_PHASE");
        }
        else if (RequestType.ACTION.equals(type))
        {
            requestMap.put(PORTLET_LIFECYCLE_PHASE, "ACTION_PHASE");
        }
        else if (RequestType.EVENT.equals(type))
        {
            requestMap.put(PORTLET_LIFECYCLE_PHASE, "EVENT_PHASE");
        }
        else if (RequestType.RESOURCE.equals(type))
        {
            requestMap.put(PORTLET_LIFECYCLE_PHASE, "RESOURCE_PHASE");
        }
        
        if (handlers == null)
        {
            //Init
            handlers = new LinkedList();
            handlers.add(new FrameworkAdapterRequestHandler());
            //TODO: put this one causes context never released because
            //in portlets different threads could handle same request (one 
            //thread action and the other one render)
            //handlers.add(new ContextLockRequestHandler());
            handlers.add(new ConversationManagerRequestHandler());
            handlers.add(new DataSourceLeakRequestHandler());
            // Add any other handlers registered by filters or similar
            Map reqScope = facesContext.getExternalContext().getRequestMap();
            handlers.addAll(ConfigUtils.getRequestHandlers(reqScope));
            
            if (RequestType.ACTION.equals(ExternalContextUtils.getRequestType(context, request)))
            {
                ActionResponse actionResponse = (ActionResponse) response;
                
                PortletRequest portletRequest = (PortletRequest) request;
                nextToken = portletRequest.getParameter(CONVERSATION_CONTEXT_PARAM);
                if (nextToken == null)
                {
                    nextToken = _getNextToken();
                    actionResponse.setRenderParameter(REQUEST_CONTEXT_PARAM, nextToken);                    
                }
                if (nextToken != null)
                {
                    //Put it on application map, to make it available on render response phase
                    externalContext.getApplicationMap().put(REQUEST_HANDLERS+nextToken, handlers);
                }
            }
            init = true;
        }

        // Do the stuff that suppose to be done by RequestParameterFacesContextFactory
        // We need to do it here, because the code requires handlers are already set.
        setRequestParameterResponseWrappedMode(context, request);
        
        final RequestHandler contextLockHandler = new ContextLockRequestHandler();
        
        //Since we override ExternalContext, install = true for this wrapper.
        return new _PortletFacesContextWrapper(facesContext, true,
                init, nextToken, handlers, contextLockHandler);
    }
    
    private void setRequestParameterResponseWrappedMode(Object context, Object request)
    {
        FrameworkAdapter nonJsfFrameworkAdapter = FrameworkAdapter.getCurrentInstance();
        if (nonJsfFrameworkAdapter != null)
        {
            if (!Boolean.TRUE.equals(nonJsfFrameworkAdapter.getRequestAttribute(
                    RequestParameterServletFilter.REQUEST_PARAM_FILTER_CALLED)))
            {
                nonJsfFrameworkAdapter.setRequestAttribute(
                        RequestParameterServletFilter.REQUEST_PARAM_RESPONSE_WRAPPED, Boolean.TRUE);
            }
        }
        else if (!getBooleanRequestValue(request, 
                RequestParameterServletFilter.REQUEST_PARAM_FILTER_CALLED))
        {
            setBooleanRequestValue(request, 
                    RequestParameterServletFilter.REQUEST_PARAM_RESPONSE_WRAPPED, Boolean.TRUE);
        }
    }
    
    private boolean getBooleanRequestValue(Object request, String key)
    {
        PortletRequest portletRequest = (PortletRequest) request;
        
        return Boolean.TRUE.equals(portletRequest.getAttribute(key));
    }

    private void setBooleanRequestValue(Object request, String key, Boolean value)
    {
        PortletRequest portletRequest = (PortletRequest) request;
        
        portletRequest.setAttribute(key,value);
    }
}
