/*
 * 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.jetspeed.container.invoker;

import java.io.IOException;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.UnavailableException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.jetspeed.PortalContext;
import org.apache.jetspeed.PortalReservedParameters;
import org.apache.jetspeed.container.FilterManager;
import org.apache.jetspeed.container.PortletWindow;
import org.apache.jetspeed.factory.PortletFactory;
import org.apache.jetspeed.factory.PortletInstance;
import org.apache.jetspeed.om.portlet.PortletApplication;
import org.apache.jetspeed.om.portlet.PortletDefinition;
import org.apache.jetspeed.om.window.impl.PortletWindowImpl;
import org.apache.jetspeed.request.JetspeedRequestContext;
import org.apache.pluto.container.PortletInvokerService;
import org.apache.pluto.container.PortletRequestContext;
import org.apache.pluto.container.PortletResponseContext;

/**
 * LocalPortletInvoker invokes local (internal) portlet applications.
 * Local portlet applications are stored within the Jetspeed Portlet application.
 * They are not separate web applications; but are stored under Jetspeed's
 * WEB-INF/apps directory.
 * <h3>Sample Configuration</h3>
 * <pre>
 * <code>
 * factory.invoker.local = org.apache.jetspeed.container.invoker.LocalPortletInvoker
 * factory.invoker.local.pool.size = 50
 * </code> 
 * </pre>
 * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
 * @version $Id: LocalPortletInvoker.java 767935 2009-04-23 14:49:02Z woonsan $
 */
public class LocalPortletInvoker implements JetspeedPortletInvoker
{
    protected PortalContext portalContext;
    protected PortletFactory portletFactory;
    protected ServletContext jetspeedContext;
    protected ServletConfig jetspeedConfig;
    protected PortletDefinition portletDefinition;
    protected boolean activated = false;
    private ContainerRequestResponseUnwrapper requestResponseUnwrapper;
    
    public LocalPortletInvoker(PortalContext portalContext, ContainerRequestResponseUnwrapper requestResponseUnwrapper)
    {
        this.portalContext = portalContext;
        this.requestResponseUnwrapper = requestResponseUnwrapper;
        activated = false;
    }
    
    public void activate(PortletFactory portletFactory, PortletDefinition portletDefinition, ServletConfig servletConfig)
    {
        this.portletFactory = portletFactory;
        this.jetspeedConfig = servletConfig;
        jetspeedContext = servletConfig.getServletContext();
        this.portletDefinition = portletDefinition;
        activated = true;        
    }

    public void passivate()
    {
        activated = false;    
    }
    
    public boolean isActivated()
    {
        return activated;
    }
    
    /**
     * Invokes the specific request denoted by the <code>method</code> parameter on a portlet.
     * The portlet is invoked with a direct method call on the portlet. It is not invoked in another web application.
     * This requires manipulation of the current thread's classpath.
     * 
     * @param requestContext
     * @param portletRequest
     * @param portletResponse
     * @param action
     * @param filter
     * @throws PortletException
     * @throws IOException
     */
    public void invoke(PortletRequestContext requestContext, PortletRequest portletRequest, PortletResponse portletResponse,
                       PortletWindow.Action action, FilterManager filter)
            throws PortletException, IOException
    {
        if (PortletWindow.Action.ACTION != action && PortletWindow.Action.RENDER != action)
        {
            return;
        }
        
        PortletWindowImpl window = (PortletWindowImpl)requestContext.getPortletWindow();
        
        PortletApplication pa = portletDefinition.getApplication();
        pa.setLocalContextPath(portalContext.getContextPath());

        ClassLoader paClassLoader = portletFactory.getPortletApplicationClassLoader(pa);
        PortletInstance portletInstance = portletFactory.getPortletInstance(jetspeedContext, portletDefinition);
        
        ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
        try
        {
            PortletResponseContext responseContext = (PortletResponseContext)portletRequest.getAttribute(PortletInvokerService.RESPONSE_CONTEXT);
            ((JetspeedRequestContext)window.getRequestContext()).setCurrentPortletWindow(window);
            
            window.setInvocationState(action, requestContext, responseContext, portletRequest, portletResponse, portletInstance);
            window.setAttribute(PortalReservedParameters.FRAGMENT_ATTRIBUTE, window.getFragment());
            window.setAttribute(PortalReservedParameters.PORTLET_WINDOW_ATTRIBUTE, window);
            window.setAttribute(PortalReservedParameters.PORTLET_DEFINITION_ATTRIBUTE, portletDefinition);
            
            // initialize request/response for portletRequestContext
            HttpServletRequest request = requestContext.getContainerRequest();
            HttpServletResponse response = requestContext.getContainerResponse();
            
            request = (HttpServletRequest) requestResponseUnwrapper.unwrapContainerRequest(request);
            response = (HttpServletResponse) requestResponseUnwrapper.unwrapContainerResponse(response);
            requestContext.init(window.getPortletInstance().getConfig(), this.jetspeedContext, request, response);
            window.getPortletResponseContext().init(request, response);
            
            Thread.currentThread().setContextClassLoader(paClassLoader);
            
            if (PortletWindow.Action.ACTION == action)
            {
                ActionRequest actionRequest = (ActionRequest) portletRequest;
                ActionResponse actionResponse = (ActionResponse) portletResponse;

                portletInstance.processAction(actionRequest, actionResponse);
            }
            else // if (PortletWindow.Action.RENDER == action)
            {
                RenderRequest renderRequest = (RenderRequest) portletRequest;
                RenderResponse renderResponse = (RenderResponse) portletResponse;
                renderResponse.setContentType(window.getRequestContext().getMimeType());
                portletInstance.render(renderRequest, renderResponse);
            }
        }
        catch (Throwable t)
        {
            if ( t instanceof UnavailableException )
            {
                // take it out of service
                try
                {
                    portletInstance.destroy();
                }
                catch (Throwable ignore)
                {
                    // never mind, it won't be used anymore
                }
            }
            if ( t instanceof PortletException )
            {
                throw (PortletException)t;
            }
            if ( t instanceof IOException )
            {
                throw (IOException)t;
            }
            else
            {
                throw new PortletException(t);
            }
        }
        finally
        {
            ((JetspeedRequestContext)window.getRequestContext()).setCurrentPortletWindow(null);
            Thread.currentThread().setContextClassLoader(oldLoader);
        }
    }
}