/*
 * 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.List;
import java.util.ListIterator;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextWrapper;

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

/**
 * Convenient class to wrap the current FacesContext in portlet environment.
 * 
 * @since 1.4
 * 
 * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
 * @version $Revision: 798382 $ $Date: 2009-07-27 22:23:02 -0500 (lun, 27 jul 2009) $
 */
public class _PortletFacesContextWrapper extends FacesContextWrapper
{
    private final static String REQUEST_ADAPTER = "org.apache.myfaces.orchestra.REQUEST_ADAPTER";

    //~ Instance fields -------------------------------------------------------

    private final FacesContext _facesContext;
    private final ExternalContext externalContextDelegate;
    private final RequestHandler contextLockHandler;
    private final List _handlers;
    private final String _nextToken;

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

    //~ Constructors ----------------------------------------------------------

    /**
     * The install parameter controls whether this object will be configured as
     * the object returned from calls to FacesContext.getCurrentInstance() or not.
     * <p>
     * When only overriding the release() method, then install=false is ok as that
     * is called directly by the FacesServlet on the instance returned by the
     * FacesContextFactory. However all other methods are invoked on the object
     * that is returned from FacesContext.getCurrentInstance, so install=true is
     * needed in order for any other method overrides to have any effect.
     * <p>
     * <b>IMPORTANT</b>: install=true should not be used until MYFACES-1820 is fixed.
     */
    public _PortletFacesContextWrapper(final FacesContext facesContext,
            final boolean install, boolean finit, String fnextToken, List fhandlers,
            final RequestHandler fcontextLockHandler )
    {
        log.debug("getFacesContext: running inner constructor");

        _facesContext = facesContext;

        if (install)
        {
            FacesContext.setCurrentInstance(this);
        }

        externalContextDelegate = new PortletExternalContextWrapper(
                _facesContext.getExternalContext());

        _handlers = fhandlers;
        _nextToken = fnextToken;
        contextLockHandler = fcontextLockHandler;
        if (finit)
        {
            ListIterator i = fhandlers.listIterator();
            try
            {
                contextLockHandler.init(facesContext);
                while (i.hasNext())
                {
                    RequestHandler h = (RequestHandler) i.next();

                    if (log.isDebugEnabled())
                    {
                        log.debug("Running inithandler of type "
                                + h.getClass().getName());
                    }

                    h.init(facesContext);
                }
            }
            catch (RuntimeException e)
            {
                log.error("Problem initialising RequestHandler", e);
                _release(i);
                contextLockHandler.deinit();
                throw e;
            }
        }
        else
        {
            try
            {
                contextLockHandler.init(facesContext);
            }
            catch (RuntimeException e)
            {
                contextLockHandler.deinit();
            }

            RequestType type = ExternalContextUtils.getRequestType(facesContext
                    .getExternalContext());

            if (RequestType.RENDER.equals(type))
            {
                String handlersKey = (String) fnextToken;
                FrameworkAdapter adapter = (FrameworkAdapter) getExternalContext()
                        .getApplicationMap().remove(
                                REQUEST_ADAPTER + handlersKey);
                if (FrameworkAdapter.getCurrentInstance() == null)
                {
                    FrameworkAdapter.setCurrentInstance(adapter);
                }
            }
        }
    }

    //~ Non-Final Methods -----------------------------------------------------

    public void release()
    {
        log.debug("Running release");
        RequestType type = ExternalContextUtils
                .getRequestType(getExternalContext());
        if (RequestType.RENDER.equals(type) || 
            RequestType.EVENT.equals(type) || 
            RequestType.RESOURCE.equals(type) || 
            this.getResponseComplete())
        {
            ListIterator i = _handlers.listIterator();
            while (i.hasNext())
            {
                i.next();
            }
            _release(i);
        }
        if (RequestType.ACTION.equals(type))
        {
            if (this.getResponseComplete())
            {
                // If response is complete by some reason, we need to
                // clean request handlers from application map. This is set
                // before an instance of this class is created.
                getExternalContext().getApplicationMap().remove(
                        PortletOrchestraFacesContextFactory.REQUEST_HANDLERS+_nextToken);
            }
            else
            {
                //Pass the current FrameworkAdapter through application map,
                //to remove it later when rendering 
                FrameworkAdapter adapter = FrameworkAdapter.getCurrentInstance();
                getExternalContext().getApplicationMap().put(
                        REQUEST_ADAPTER + _nextToken, adapter);
                
                //Orchestra suppose the same thread handles the current request, but
                //in portlets this is not necessary true. One thread could handle action
                //requests and other render request. To keep code working we set it to
                //null here, so other request don't mix it.
                FrameworkAdapter.setCurrentInstance(null);
            }
        }

        try
        {
            //Since in portlets the same thread does not handler both action and
            //render phase for the same request contextLockHandler needs to
            //be cleared and lock again
            contextLockHandler.deinit();
        }
        catch (Exception e)
        {
            log.error("Problem deinitialising RequestHandler", e);
        }
        log.debug("Release completed");
        getWrapped().release();
    }
    
    @Override
    public FacesContext getWrapped()
    {
        return _facesContext;
    }

    private void _release(ListIterator i)
    {
        while (i.hasPrevious())
        {
            try
            {
                RequestHandler h = (RequestHandler) i.previous();
                if (log.isDebugEnabled())
                {
                    log.debug("Running deinithandler of type "
                            + h.getClass().getName());
                }
                h.deinit();
            }
            catch (Exception e)
            {
                log.error("Problem deinitialising RequestHandler", e);
            }
        }
    }

    //~ Final Methods ---------------------------------------------------------

    public ExternalContext getExternalContext()
    {
        return externalContextDelegate == null ? getWrapped()
                .getExternalContext() : externalContextDelegate;
    }
}
