org.jasig.portal.portlet.rendering.PortletRendererImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.portal.portlet.rendering.PortletRendererImpl.java

Source

/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig 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.jasig.portal.portlet.rendering;

import java.io.IOException;
import java.io.Writer;

import javax.portlet.CacheControl;
import javax.portlet.Event;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pluto.container.PortletContainer;
import org.apache.pluto.container.PortletContainerException;
import org.jasig.portal.AuthorizationException;
import org.jasig.portal.EntityIdentifier;
import org.jasig.portal.api.portlet.PortletDelegationLocator;
import org.jasig.portal.events.IPortletExecutionEventFactory;
import org.jasig.portal.portlet.PortletDispatchException;
import org.jasig.portal.portlet.container.cache.CacheControlImpl;
import org.jasig.portal.portlet.container.cache.CacheState;
import org.jasig.portal.portlet.container.cache.CachedPortletData;
import org.jasig.portal.portlet.container.cache.CachedPortletResourceData;
import org.jasig.portal.portlet.container.cache.CachingPortletOutputHandler;
import org.jasig.portal.portlet.container.cache.CachingPortletResourceOutputHandler;
import org.jasig.portal.portlet.container.cache.HeaderSettingCacheControl;
import org.jasig.portal.portlet.container.cache.IPortletCacheControlService;
import org.jasig.portal.portlet.container.cache.PortletCachingHeaderUtils;
import org.jasig.portal.portlet.container.services.AdministrativeRequestListenerController;
import org.jasig.portal.portlet.om.IPortletDefinition;
import org.jasig.portal.portlet.om.IPortletEntity;
import org.jasig.portal.portlet.om.IPortletWindow;
import org.jasig.portal.portlet.om.IPortletWindowId;
import org.jasig.portal.portlet.registry.IPortletWindowRegistry;
import org.jasig.portal.portlet.session.PortletSessionAdministrativeRequestListener;
import org.jasig.portal.security.IAuthorizationPrincipal;
import org.jasig.portal.security.IPerson;
import org.jasig.portal.security.IPersonManager;
import org.jasig.portal.services.AuthorizationService;
import org.jasig.portal.url.IPortalRequestInfo;
import org.jasig.portal.url.IPortletUrlBuilder;
import org.jasig.portal.url.IUrlSyntaxProvider;
import org.jasig.portal.url.ParameterMap;
import org.jasig.portal.utils.web.PortletHttpServletRequestWrapper;
import org.jasig.portal.utils.web.PortletHttpServletResponseWrapper;
import org.jasig.portal.utils.web.PortletMimeHttpServletResponseWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Executes methods on portlets using Pluto
 * 
 * @author Eric Dalquist
 * @version $Revision$
 */
@Service
public class PortletRendererImpl implements IPortletRenderer {
    protected final Log logger = LogFactory.getLog(this.getClass());

    private IPersonManager personManager;
    private IPortletWindowRegistry portletWindowRegistry;
    private PortletContainer portletContainer;
    private PortletDelegationLocator portletDelegationLocator;
    private IPortletCacheControlService portletCacheControlService;
    private IPortletExecutionEventFactory portalEventFactory;
    private IUrlSyntaxProvider urlSyntaxProvider;

    @Autowired
    public void setUrlSyntaxProvider(IUrlSyntaxProvider urlSyntaxProvider) {
        this.urlSyntaxProvider = urlSyntaxProvider;
    }

    @Autowired
    public void setPortalEventFactory(IPortletExecutionEventFactory portalEventFactory) {
        this.portalEventFactory = portalEventFactory;
    }

    @Autowired
    public void setPersonManager(IPersonManager personManager) {
        this.personManager = personManager;
    }

    @Autowired
    public void setPortletWindowRegistry(IPortletWindowRegistry portletWindowRegistry) {
        this.portletWindowRegistry = portletWindowRegistry;
    }

    @Autowired
    public void setPortletContainer(PortletContainer portletContainer) {
        this.portletContainer = portletContainer;
    }

    @Autowired
    public void setPortletDelegationLocator(PortletDelegationLocator portletDelegationLocator) {
        this.portletDelegationLocator = portletDelegationLocator;
    }

    /**
    * @param portletCacheControlService the portletCacheControlService to set
    */
    @Autowired
    public void setPortletCacheControlService(IPortletCacheControlService portletCacheControlService) {
        this.portletCacheControlService = portletCacheControlService;
    }

    /*
     * PLT 22.1 If the content of a portlet is cached and the portlet is target of request 
      * with an action-type semantic (e.g. an action or event call), the portlet container should discard the cache and
      * invoke the corresponding request handling methods of the portlet like processAction,or processEvent.
      * 
     *  (non-Javadoc)
      * @see org.jasig.portal.channels.portlet.IPortletRenderer#doAction(org.jasig.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
    @Override
    public long doAction(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse) {
        this.portletCacheControlService.purgeCachedPortletData(portletWindowId, httpServletRequest);

        final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest,
                portletWindowId);

        httpServletRequest = this.setupPortletRequest(httpServletRequest);
        httpServletResponse = new PortletHttpServletResponseWrapper(httpServletResponse, portletWindow);

        //Execute the action, 
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Executing portlet action for window '" + portletWindow + "'");
        }

        final long start = System.nanoTime();
        try {
            this.portletContainer.doAction(portletWindow.getPlutoPortletWindow(), httpServletRequest,
                    httpServletResponse);
        } catch (PortletException pe) {
            throw new PortletDispatchException(
                    "The portlet window '" + portletWindow + "' threw an exception while executing action.",
                    portletWindow, pe);
        } catch (PortletContainerException pce) {
            throw new PortletDispatchException(
                    "The portlet container threw an exception while executing action on portlet window '"
                            + portletWindow + "'.",
                    portletWindow, pce);
        } catch (IOException ioe) {
            throw new PortletDispatchException(
                    "The portlet window '" + portletWindow + "' threw an exception while executing action.",
                    portletWindow, ioe);
        }

        final long executionTime = System.nanoTime() - start;

        this.portalEventFactory.publishPortletActionExecutionEvent(httpServletRequest, this, portletWindowId,
                executionTime);

        return executionTime;
    }

    /*
     * PLT 22.1 If the content of a portlet is cached and the portlet is target of request 
     * with an action-type semantic (e.g. an action or event call), the portlet container should discard the cache and
     * invoke the corresponding request handling methods of the portlet like processAction,or processEvent.
     * 
     * (non-Javadoc)
     * @see org.jasig.portal.portlet.rendering.IPortletRenderer#doEvent(org.jasig.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.portlet.Event)
     */
    @Override
    public long doEvent(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse, Event event) {
        this.portletCacheControlService.purgeCachedPortletData(portletWindowId, httpServletRequest);

        final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest,
                portletWindowId);

        httpServletRequest = this.setupPortletRequest(httpServletRequest);
        httpServletResponse = new PortletHttpServletResponseWrapper(httpServletResponse, portletWindow);

        //Execute the action, 
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Executing portlet event for window '" + portletWindow + "'");
        }

        final long start = System.nanoTime();
        try {
            this.portletContainer.doEvent(portletWindow.getPlutoPortletWindow(), httpServletRequest,
                    httpServletResponse, event);
        } catch (PortletException pe) {
            throw new PortletDispatchException(
                    "The portlet window '" + portletWindow + "' threw an exception while executing event.",
                    portletWindow, pe);
        } catch (PortletContainerException pce) {
            throw new PortletDispatchException(
                    "The portlet container threw an exception while executing event on portlet window '"
                            + portletWindow + "'.",
                    portletWindow, pce);
        } catch (IOException ioe) {
            throw new PortletDispatchException(
                    "The portlet window '" + portletWindow + "' threw an exception while executing event.",
                    portletWindow, ioe);
        }

        final long executionTime = System.nanoTime() - start;

        this.portalEventFactory.publishPortletEventExecutionEvent(httpServletRequest, this, portletWindowId,
                executionTime, event.getQName());

        return executionTime;
    }

    @Override
    public PortletRenderResult doRenderHeader(IPortletWindowId portletWindowId,
            HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
            PortletOutputHandler portletOutputHandler) throws IOException {

        return doRender(portletWindowId, httpServletRequest, httpServletResponse, portletOutputHandler,
                RenderPart.HEADERS);
    }

    /**
     * Interacts with the {@link IPortletCacheControlService} to determine if the markup should come from cache or not.
     * If cached data doesn't exist or is expired, this delegates to {@link #doRenderMarkupInternal(IPortletWindowId, HttpServletRequest, HttpServletResponse, Writer)}.
     * @throws IOException 
     */
    @Override
    public PortletRenderResult doRenderMarkup(IPortletWindowId portletWindowId,
            HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
            PortletOutputHandler portletOutputHandler) throws IOException {

        return doRender(portletWindowId, httpServletRequest, httpServletResponse, portletOutputHandler,
                RenderPart.MARKUP);
    }

    /**
     * Describes the part of the render request and defines the part specific behaviors
     */
    protected enum RenderPart {
        HEADERS(PortletRequest.RENDER_HEADERS) {
            @Override
            public CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> getCacheState(
                    IPortletCacheControlService portletCacheControlService, HttpServletRequest request,
                    IPortletWindowId portletWindowId) {
                return portletCacheControlService.getPortletRenderHeaderState(request, portletWindowId);
            }

            @Override
            public void cachePortletOutput(IPortletCacheControlService portletCacheControlService,
                    IPortletWindowId portletWindowId, HttpServletRequest request,
                    CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState,
                    CachedPortletData<PortletRenderResult> cachedPortletData) {

                portletCacheControlService.cachePortletRenderHeaderOutput(portletWindowId, request, cacheState,
                        cachedPortletData);
            }

            @Override
            public void publishRenderExecutionEvent(IPortletExecutionEventFactory portalEventFactory,
                    PortletRendererImpl source, HttpServletRequest request, IPortletWindowId portletWindowId,
                    long executionTime, boolean targeted, boolean cached) {

                portalEventFactory.publishPortletRenderHeaderExecutionEvent(request, source, portletWindowId,
                        executionTime, targeted, cached);
            }
        },
        MARKUP(PortletRequest.RENDER_MARKUP) {
            @Override
            public CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> getCacheState(
                    IPortletCacheControlService portletCacheControlService, HttpServletRequest request,
                    IPortletWindowId portletWindowId) {
                return portletCacheControlService.getPortletRenderState(request, portletWindowId);
            }

            @Override
            public void cachePortletOutput(IPortletCacheControlService portletCacheControlService,
                    IPortletWindowId portletWindowId, HttpServletRequest request,
                    CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState,
                    CachedPortletData<PortletRenderResult> cachedPortletData) {

                portletCacheControlService.cachePortletRenderOutput(portletWindowId, request, cacheState,
                        cachedPortletData);
            }

            @Override
            public void publishRenderExecutionEvent(IPortletExecutionEventFactory portalEventFactory,
                    PortletRendererImpl source, HttpServletRequest request, IPortletWindowId portletWindowId,
                    long executionTime, boolean targeted, boolean cached) {
                portalEventFactory.publishPortletRenderExecutionEvent(request, source, portletWindowId,
                        executionTime, targeted, cached);
            }
        };

        private final String renderPart;

        private RenderPart(String renderPart) {
            this.renderPart = renderPart;
        }

        /**
         * Get the cache state for the request
         */
        public abstract CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> getCacheState(
                IPortletCacheControlService portletCacheControlService, HttpServletRequest request,
                IPortletWindowId portletWindowId);

        /**
         * Cache the portlet output
         */
        public abstract void cachePortletOutput(IPortletCacheControlService portletCacheControlService,
                IPortletWindowId portletWindowId, HttpServletRequest request,
                CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState,
                CachedPortletData<PortletRenderResult> cachedPortletData);

        /**
         * Public portlet event
         */
        public abstract void publishRenderExecutionEvent(IPortletExecutionEventFactory portalEventFactory,
                PortletRendererImpl source, HttpServletRequest request, IPortletWindowId portletWindowId,
                long executionTime, boolean targeted, boolean cached);

        /**
         * @return The {@link PortletRequest#RENDER_PART} name
         */
        public final String getRenderPart() {
            return renderPart;
        }

        /**
         * Determine the {@link RenderPart} from the {@link PortletRequest#RENDER_PART}
         */
        public static RenderPart getRenderPart(String renderPart) {
            if (PortletRequest.RENDER_HEADERS.equals(renderPart)) {
                return HEADERS;
            }

            if (PortletRequest.RENDER_MARKUP.equals(renderPart)) {
                return MARKUP;
            }

            throw new IllegalArgumentException(
                    "Unknown " + PortletRequest.RENDER_PART + " specified: " + renderPart);
        }
    }

    protected PortletRenderResult doRender(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse, PortletOutputHandler portletOutputHandler,
            RenderPart renderPart) throws IOException {

        final CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState = renderPart
                .getCacheState(this.portletCacheControlService, httpServletRequest, portletWindowId);

        final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest,
                portletWindowId);

        /*
         * If the portlet is rendering in EXCLUSIVE WindowState ignore the provided PortletOutputHandler and
         * write directly to the response.
         * 
         * THIS IS VERY BAD AND SHOULD BE DEPRECATED ALONG WITH EXCLUSIVE WINDOW STATE
         */
        if (EXCLUSIVE.equals(portletWindow.getWindowState())) {
            portletOutputHandler = new ResourcePortletOutputHandler(httpServletResponse);
        }

        if (cacheState.isUseCachedData()) {
            return doRenderReplayCachedContent(portletWindow, httpServletRequest, cacheState, portletOutputHandler,
                    renderPart, 0);
        }

        final int cacheSizeThreshold = this.portletCacheControlService.getCacheSizeThreshold();
        final CachingPortletOutputHandler cachingPortletOutputHandler = new CachingPortletOutputHandler(
                portletOutputHandler, cacheSizeThreshold);

        final CacheControl cacheControl = cacheState.getCacheControl();

        //Setup the request and response
        httpServletRequest = this.setupPortletRequest(httpServletRequest);
        httpServletResponse = new PortletMimeHttpServletResponseWrapper(httpServletResponse, portletWindow,
                portletOutputHandler, cacheControl);

        httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_CACHE_CONTROL, cacheControl);
        httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_OUTPUT_HANDLER, cachingPortletOutputHandler);

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Rendering portlet body for window '" + portletWindow + "'");
        }

        final long renderStartTime = System.nanoTime();
        try {
            httpServletRequest.setAttribute(PortletRequest.RENDER_PART, renderPart.getRenderPart());
            this.portletContainer.doRender(portletWindow.getPlutoPortletWindow(), httpServletRequest,
                    httpServletResponse);
        } catch (PortletException pe) {
            throw new PortletDispatchException(
                    "The portlet window '" + portletWindow + "' threw an exception while executing renderMarkup.",
                    portletWindow, pe);
        } catch (PortletContainerException pce) {
            throw new PortletDispatchException(
                    "The portlet container threw an exception while executing renderMarkup on portlet window '"
                            + portletWindow + "'.",
                    portletWindow, pce);
        } catch (IOException ioe) {
            throw new PortletDispatchException(
                    "The portlet window '" + portletWindow + "' threw an exception while executing renderMarkup.",
                    portletWindow, ioe);
        }

        final long executionTime = System.nanoTime() - renderStartTime;

        //See if the portlet signaled to use the cached content
        final boolean useCachedContent = cacheControl.useCachedContent();
        if (useCachedContent) {
            final CachedPortletData<PortletRenderResult> cachedPortletData = cacheState.getCachedPortletData();
            if (cachedPortletData == null) {
                throw new PortletDispatchException("The portlet window '" + portletWindow
                        + "' indicated via CacheControl#useCachedContent "
                        + "that the portal should render cached content, however there is no cached content to return. "
                        + "This is a portlet bug.", portletWindow);
            }

            //Update the expiration time and re-store in the cache
            cachedPortletData.updateExpirationTime(cacheControl.getExpirationTime());

            renderPart.cachePortletOutput(portletCacheControlService, portletWindowId, httpServletRequest,
                    cacheState, cachedPortletData);

            return doRenderReplayCachedContent(portletWindow, httpServletRequest, cacheState, portletOutputHandler,
                    renderPart, executionTime);
        }

        publishRenderEvent(portletWindow, httpServletRequest, renderPart, executionTime, false);

        //Build the render result
        final PortletRenderResult portletRenderResult = constructPortletRenderResult(httpServletRequest,
                executionTime);

        //Check if the portlet's output should be cached
        if (cacheState != null) {
            boolean shouldCache = this.portletCacheControlService.shouldOutputBeCached(cacheControl);

            if (shouldCache) {
                final CachedPortletData<PortletRenderResult> cachedPortletData = cachingPortletOutputHandler
                        .getCachedPortletData(portletRenderResult, cacheControl);

                if (cachedPortletData != null) {
                    renderPart.cachePortletOutput(portletCacheControlService, portletWindowId, httpServletRequest,
                            cacheState, cachedPortletData);
                }
            }
        }

        return portletRenderResult;
    }

    /**
     * Replay the cached content inside the {@link CachedPortletData} as the response to a doRender.
     */
    protected PortletRenderResult doRenderReplayCachedContent(IPortletWindow portletWindow,
            HttpServletRequest httpServletRequest,
            CacheState<CachedPortletData<PortletRenderResult>, PortletRenderResult> cacheState,
            PortletOutputHandler portletOutputHandler, RenderPart renderPart, long baseExecutionTime)
            throws IOException {

        if (logger.isDebugEnabled()) {
            logger.debug("Replaying cached content for Render " + renderPart + " request to " + portletWindow);
        }

        final long renderStartTime = System.nanoTime();

        final CachedPortletData<PortletRenderResult> cachedPortletData = cacheState.getCachedPortletData();
        cachedPortletData.replay(portletOutputHandler);

        final long executionTime = baseExecutionTime + (System.nanoTime() - renderStartTime);

        publishRenderEvent(portletWindow, httpServletRequest, renderPart, executionTime, true);

        final PortletRenderResult portletResult = cachedPortletData.getPortletResult();
        return new PortletRenderResult(portletResult, executionTime);
    }

    /**
     * Publish the portlet render event
     */
    protected void publishRenderEvent(IPortletWindow portletWindow, HttpServletRequest httpServletRequest,
            RenderPart renderPart, long executionTime, boolean cached) {

        final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();

        //Determine if the portlet was targeted
        final IPortalRequestInfo portalRequestInfo = this.urlSyntaxProvider
                .getPortalRequestInfo(httpServletRequest);
        final boolean targeted = portletWindowId.equals(portalRequestInfo.getTargetedPortletWindowId());

        renderPart.publishRenderExecutionEvent(this.portalEventFactory, this, httpServletRequest, portletWindowId,
                executionTime, targeted, cached);
    }

    /**
     * Construct a {@link PortletRenderResult} from information in the {@link HttpServletRequest}.
     * The second argument is how long the render action took.
     * 
     * @param httpServletRequest
     * @param renderTime
     * @return an appropriate {@link PortletRenderResult}, never null
     */
    protected PortletRenderResult constructPortletRenderResult(HttpServletRequest httpServletRequest,
            long renderTime) {
        final String title = (String) httpServletRequest.getAttribute(IPortletRenderer.ATTRIBUTE__PORTLET_TITLE);
        final String newItemCountString = (String) httpServletRequest
                .getAttribute(IPortletRenderer.ATTRIBUTE__PORTLET_NEW_ITEM_COUNT);
        final int newItemCount;
        if (newItemCountString != null && StringUtils.isNumeric(newItemCountString)) {
            newItemCount = Integer.parseInt(newItemCountString);
        } else {
            newItemCount = 0;
        }
        final String link = (String) httpServletRequest.getAttribute(IPortletRenderer.ATTRIBUTE__PORTLET_LINK);

        return new PortletRenderResult(title, link, newItemCount, renderTime);
    }

    @Override
    public long doServeResource(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse, PortletResourceOutputHandler portletOutputHandler)
            throws IOException {

        final CacheState<CachedPortletResourceData<Long>, Long> cacheState = this.portletCacheControlService
                .getPortletResourceState(httpServletRequest, portletWindowId);

        final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest,
                portletWindowId);

        if (cacheState.isUseBrowserData()) {
            return doResourceReplayBrowserContent(portletWindow, httpServletRequest, cacheState,
                    portletOutputHandler);
        }

        if (cacheState.isUseCachedData()) {
            return doResourceReplayCachedContent(portletWindow, httpServletRequest, cacheState,
                    portletOutputHandler, 0);
        }

        final int cacheSizeThreshold = this.portletCacheControlService.getCacheSizeThreshold();
        final CachingPortletResourceOutputHandler cachingPortletOutputHandler = new CachingPortletResourceOutputHandler(
                portletOutputHandler, cacheSizeThreshold);

        CacheControl cacheControl = cacheState.getCacheControl();
        //Wrap the cache control so it immediately sets the caching related response headers
        cacheControl = new HeaderSettingCacheControl(cacheControl, cachingPortletOutputHandler);

        //Setup the request and response
        httpServletRequest = this.setupPortletRequest(httpServletRequest);
        httpServletResponse = new PortletResourceHttpServletResponseWrapper(httpServletResponse, portletWindow,
                portletOutputHandler, cacheControl);

        httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_CACHE_CONTROL, cacheControl);
        httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_OUTPUT_HANDLER, cachingPortletOutputHandler);

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Executing resource request for window '" + portletWindow + "'");
        }

        final long start = System.nanoTime();
        try {
            this.portletContainer.doServeResource(portletWindow.getPlutoPortletWindow(), httpServletRequest,
                    httpServletResponse);
        } catch (PortletException pe) {
            throw new PortletDispatchException(
                    "The portlet window '" + portletWindow + "' threw an exception while executing serveResource.",
                    portletWindow, pe);
        } catch (PortletContainerException pce) {
            throw new PortletDispatchException(
                    "The portlet container threw an exception while executing serveResource on portlet window '"
                            + portletWindow + "'.",
                    portletWindow, pce);
        } catch (IOException ioe) {
            throw new PortletDispatchException(
                    "The portlet window '" + portletWindow + "' threw an exception while executing serveResource.",
                    portletWindow, ioe);
        }
        final long executionTime = System.nanoTime() - start;

        //See if the portlet signaled to use the cached content
        final boolean useCachedContent = cacheControl.useCachedContent();
        if (useCachedContent) {
            final CachedPortletResourceData<Long> cachedPortletResourceData = cacheState.getCachedPortletData();

            if (cachedPortletResourceData != null) {
                //Update the expiration time and re-store in the cache
                final CachedPortletData<Long> cachedPortletData = cachedPortletResourceData.getCachedPortletData();
                cachedPortletData.updateExpirationTime(cacheControl.getExpirationTime());
                this.portletCacheControlService.cachePortletResourceOutput(portletWindowId, httpServletRequest,
                        cacheState, cachedPortletResourceData);
            }

            if (cacheState.isBrowserSetEtag()) {
                //Browser-side content matches, send a 304
                return doResourceReplayBrowserContent(portletWindow, httpServletRequest, cacheState,
                        portletOutputHandler);
            }

            return doResourceReplayCachedContent(portletWindow, httpServletRequest, cacheState,
                    cachingPortletOutputHandler, executionTime);
        }

        publishResourceEvent(portletWindow, httpServletRequest, executionTime, false, false);

        if (cacheState != null) {
            boolean shouldCache = this.portletCacheControlService.shouldOutputBeCached(cacheControl);

            if (shouldCache) {
                final CachedPortletResourceData<Long> cachedPortletResourceData = cachingPortletOutputHandler
                        .getCachedPortletResourceData(executionTime, cacheControl);

                if (cachedPortletResourceData != null) {
                    this.portletCacheControlService.cachePortletResourceOutput(portletWindowId, httpServletRequest,
                            cacheState, cachedPortletResourceData);
                }
            }
        }

        return executionTime;
    }

    protected long doResourceReplayBrowserContent(IPortletWindow portletWindow,
            HttpServletRequest httpServletRequest, CacheState<CachedPortletResourceData<Long>, Long> cacheState,
            PortletResourceOutputHandler portletOutputHandler) {

        if (logger.isDebugEnabled()) {
            logger.debug("Sending 304 for resource request to " + portletWindow);
        }

        if (portletOutputHandler.isCommitted()) {
            throw new IllegalStateException("Attempting to send 304 but response is already committed");
        }

        final long start = System.nanoTime();

        portletOutputHandler.setStatus(HttpServletResponse.SC_NOT_MODIFIED);

        final CachedPortletResourceData<Long> cachedPortletResourceData = cacheState.getCachedPortletData();
        if (cachedPortletResourceData != null) {
            //Freshen up the various caching related headers
            final CachedPortletData<Long> cachedPortletData = cachedPortletResourceData.getCachedPortletData();
            PortletCachingHeaderUtils.setCachingHeaders(cachedPortletData, portletOutputHandler);
        }

        final long executionTime = System.nanoTime() - start;

        publishResourceEvent(portletWindow, httpServletRequest, executionTime, true, false);

        return executionTime;
    }

    /**
     * Replay the cached content inside the {@link CachedPortletData} as the response to a doResource.
     */
    protected Long doResourceReplayCachedContent(IPortletWindow portletWindow,
            HttpServletRequest httpServletRequest, CacheState<CachedPortletResourceData<Long>, Long> cacheState,
            PortletResourceOutputHandler portletOutputHandler, long baseExecutionTime) throws IOException {

        if (logger.isDebugEnabled()) {
            logger.debug("Replaying cached content for resource request to " + portletWindow);
        }

        final long renderStartTime = System.nanoTime();

        final CachedPortletResourceData<Long> cachedPortletResourceData = cacheState.getCachedPortletData();
        if (cachedPortletResourceData == null) {
            throw new PortletDispatchException("The portlet window '" + portletWindow
                    + "' indicated via CacheControl#useCachedContent "
                    + "that the portal should render cached content, however there is no cached content to return. "
                    + "This is a portlet bug.", portletWindow);
        }

        cachedPortletResourceData.replay(portletOutputHandler);

        final long executionTime = baseExecutionTime + (System.nanoTime() - renderStartTime);

        publishResourceEvent(portletWindow, httpServletRequest, executionTime, false, true);

        return executionTime;
    }

    /**
     * Publish the portlet resource event
     */
    protected void publishResourceEvent(IPortletWindow portletWindow, HttpServletRequest httpServletRequest,
            long executionTime, boolean usedBrowserCache, boolean usedPortalCache) {

        final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();

        this.portalEventFactory.publishPortletResourceExecutionEvent(httpServletRequest, this, portletWindowId,
                executionTime, usedBrowserCache, usedPortalCache);
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.portal.portlet.rendering.IPortletRenderer#doReset(org.jasig.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void doReset(IPortletWindowId portletWindowId, HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse) {
        final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(httpServletRequest,
                portletWindowId);
        if (portletWindow != null) {
            portletWindow.setPortletMode(PortletMode.VIEW);
            portletWindow.setRenderParameters(new ParameterMap());
            portletWindow.setExpirationCache(null);

            httpServletRequest = this.setupPortletRequest(httpServletRequest);
            httpServletResponse = new PortletHttpServletResponseWrapper(httpServletResponse, portletWindow);

            httpServletRequest.setAttribute(AdministrativeRequestListenerController.DEFAULT_LISTENER_KEY_ATTRIBUTE,
                    "sessionActionListener");
            httpServletRequest.setAttribute(PortletSessionAdministrativeRequestListener.ACTION,
                    PortletSessionAdministrativeRequestListener.SessionAction.CLEAR);
            httpServletRequest.setAttribute(PortletSessionAdministrativeRequestListener.SCOPE,
                    PortletSession.PORTLET_SCOPE);

            //TODO modify PortletContainer.doAdmin to create a specific "admin" req/res object and context so we don't have to fake it with a render req
            //These are required for a render request to be created and admin requests use a render request under the hood
            final String characterEncoding = httpServletResponse.getCharacterEncoding();
            httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_OUTPUT_HANDLER,
                    new RenderPortletOutputHandler(characterEncoding));
            httpServletRequest.setAttribute(ATTRIBUTE__PORTLET_CACHE_CONTROL, new CacheControlImpl());

            try {
                this.portletContainer.doAdmin(portletWindow.getPlutoPortletWindow(), httpServletRequest,
                        httpServletResponse);
            } catch (PortletException pe) {
                throw new PortletDispatchException(
                        "The portlet window '" + portletWindow
                                + "' threw an exception while executing admin command to clear session.",
                        portletWindow, pe);
            } catch (PortletContainerException pce) {
                throw new PortletDispatchException(
                        "The portlet container threw an exception while executing admin command to clear session on portlet window '"
                                + portletWindow + "'.",
                        portletWindow, pce);
            } catch (IOException ioe) {
                throw new PortletDispatchException(
                        "The portlet window '" + portletWindow
                                + "' threw an exception while executing admin command to clear session.",
                        portletWindow, ioe);
            }
        } else {
            logger.debug(
                    "ignoring doReset as portletWindowRegistry#getPortletWindow returned a null result for portletWindowId "
                            + portletWindowId);
        }
    }

    protected HttpServletRequest setupPortletRequest(HttpServletRequest httpServletRequest) {
        final PortletHttpServletRequestWrapper portletHttpServletRequestWrapper = new PortletHttpServletRequestWrapper(
                httpServletRequest);
        portletHttpServletRequestWrapper.setAttribute(PortletDelegationLocator.PORTLET_DELECATION_LOCATOR_ATTR,
                this.portletDelegationLocator);

        return portletHttpServletRequestWrapper;
    }

    protected void setupPortletWindow(HttpServletRequest httpServletRequest, IPortletWindow portletWindow,
            IPortletUrlBuilder portletUrl) {
        final PortletMode portletMode = portletUrl.getPortletMode();
        if (portletMode != null) {
            if (IPortletRenderer.CONFIG.equals(portletMode)) {
                final IPerson person = this.personManager.getPerson(httpServletRequest);

                final EntityIdentifier ei = person.getEntityIdentifier();
                final AuthorizationService authorizationService = AuthorizationService.instance();
                final IAuthorizationPrincipal ap = authorizationService.newPrincipal(ei.getKey(), ei.getType());

                final IPortletEntity portletEntity = portletWindow.getPortletEntity();
                final IPortletDefinition portletDefinition = portletEntity.getPortletDefinition();

                if (!ap.canConfigure(portletDefinition.getPortletDefinitionId().getStringId())) {
                    throw new AuthorizationException(person.getUserName() + " does not have permission to render '"
                            + portletDefinition.getFName() + "' in " + portletMode + " PortletMode");
                }
            }
        }
    }

}