org.apereo.portal.portlet.rendering.PortletExecutionManager.java Source code

Java tutorial

Introduction

Here is the source code for org.apereo.portal.portlet.rendering.PortletExecutionManager.java

Source

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

import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javax.portlet.Event;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.google.common.base.Function;
import org.apache.commons.lang.StringUtils;
import org.apache.pluto.container.om.portlet.ContainerRuntimeOption;
import org.apache.pluto.container.om.portlet.PortletDefinition;
import org.apereo.portal.events.IPortletExecutionEventFactory;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.portlet.om.IPortletDefinitionParameter;
import org.apereo.portal.portlet.om.IPortletDescriptorKey;
import org.apereo.portal.portlet.om.IPortletEntity;
import org.apereo.portal.portlet.om.IPortletWindow;
import org.apereo.portal.portlet.om.IPortletWindowId;
import org.apereo.portal.portlet.om.PortletLifecycleState;
import org.apereo.portal.portlet.registry.IPortletWindowRegistry;
import org.apereo.portal.portlet.rendering.worker.IPortletExecutionContext;
import org.apereo.portal.portlet.rendering.worker.IPortletExecutionInterceptor;
import org.apereo.portal.portlet.rendering.worker.IPortletExecutionWorker;
import org.apereo.portal.portlet.rendering.worker.IPortletFailureExecutionWorker;
import org.apereo.portal.portlet.rendering.worker.IPortletRenderExecutionWorker;
import org.apereo.portal.portlet.rendering.worker.IPortletWorkerFactory;
import org.apereo.portal.portlets.error.MaintenanceModeException;
import org.apereo.portal.utils.ConcurrentMapUtils;
import org.apereo.portal.utils.web.PortalWebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.util.WebUtils;

/**
 * Handles the asynchronous execution of portlets, handling execution errors and publishing
 * events about the execution.
 * 
 * @author Eric Dalquist
 */
@ManagedResource("uPortal:section=Framework,name=PortletExecutionManager")
@Service("portletExecutionManager")
public class PortletExecutionManager extends HandlerInterceptorAdapter
        implements IPortletExecutionManager, IPortletExecutionInterceptor, PortletExecutionManagerMXBean {

    /**
     * Optional publishing parameter that makes a portlet ineligable to send or
     * receive events.  Improves performance when they are not needed.
     */
    public static final String DISABLE_PORTLET_EVENTS_PARAMETER = "disablePortletEvents";

    private static final long DEBUG_TIMEOUT = TimeUnit.HOURS.toMillis(1);
    private static final String PORTLET_HEADER_RENDERING_MAP = PortletExecutionManager.class.getName()
            + ".PORTLET_HEADER_RENDERING_MAP";
    private static final String PORTLET_RENDERING_MAP = PortletExecutionManager.class.getName()
            + ".PORTLET_RENDERING_MAP";

    protected static final String SESSION_ATTRIBUTE__PORTLET_FAILURE_CAUSE_MAP = PortletExecutionManager.class
            .getName() + ".PORTLET_FAILURE_CAUSE_MAP";

    /**
     * 'javax.portlet.renderHeaders' is the name of a container runtime option a JSR-286 portlet can enable to trigger header output
     */
    protected static final String PORTLET_RENDER_HEADERS_OPTION = "javax.portlet.renderHeaders";

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * Queue used to track workers that did not complete in their allotted time.
     */
    private final Queue<IPortletExecutionWorker<?>> hungWorkers = new ConcurrentLinkedQueue<IPortletExecutionWorker<?>>();

    private final ConcurrentMap<IPortletDescriptorKey, AtomicInteger> executionCount = ConcurrentMapUtils
            .makeDefaultsMap(new Function<IPortletDescriptorKey, AtomicInteger>() {
                public AtomicInteger apply(IPortletDescriptorKey key) {
                    return new AtomicInteger();
                }
            });

    private boolean ignoreTimeouts = false;
    private int extendedTimeoutExecutions = 5;
    private long extendedTimeoutMultiplier = 20;
    private int maxEventIterations = 100;
    private IPortletWindowRegistry portletWindowRegistry;
    private IPortletEventCoordinationService eventCoordinationService;
    private IPortletWorkerFactory portletWorkerFactory;
    private IPortletExecutionEventFactory portletExecutionEventFactory;

    /**
     * @param maxEventIterations The maximum number of iterations to spend dispatching events. Defaults to 100
     */
    @Override
    @Value("${org.apereo.portal.portlet.maxEventIterations:100}")
    public void setMaxEventIterations(int maxEventIterations) {
        this.maxEventIterations = maxEventIterations;
    }

    @Override
    public int getMaxEventIterations() {
        return this.maxEventIterations;
    }

    @Override
    @Value("${org.apereo.portal.portlet.ignoreTimeout}")
    public void setIgnoreTimeouts(boolean ignoreTimeouts) {
        this.ignoreTimeouts = ignoreTimeouts;
    }

    @Override
    public boolean isIgnoreTimeouts() {
        return this.ignoreTimeouts;
    }

    @Override
    @Value("${org.apereo.portal.portlet.extendedTimeoutExecutions:5}")
    public void setExtendedTimeoutExecutions(int extendedTimeoutExecutions) {
        this.extendedTimeoutExecutions = extendedTimeoutExecutions;
    }

    @Override
    public int getExtendedTimeoutExecutions() {
        return this.extendedTimeoutExecutions;
    }

    @Override
    @Value("${org.apereo.portal.portlet.extendedTimeoutMultiplier:20}")
    public void setExtendedTimeoutMultiplier(long extendedTimeoutMultiplier) {
        this.extendedTimeoutMultiplier = extendedTimeoutMultiplier;
    }

    @Override
    public long getExtendedTimeoutMultiplier() {
        return this.extendedTimeoutMultiplier;
    }

    @Override
    public Map<String, Integer> getPortletExecutionCounts() {
        final Map<String, Integer> counts = new TreeMap<String, Integer>();

        for (final Map.Entry<IPortletDescriptorKey, AtomicInteger> entry : this.executionCount.entrySet()) {
            final IPortletDescriptorKey key = entry.getKey();
            final AtomicInteger value = entry.getValue();
            counts.put(key.getWebAppName() + "/" + key.getPortletName(), value.get());
        }

        return counts;
    }

    @Autowired
    public void setPortletWorkerFactory(IPortletWorkerFactory portletWorkerFactory) {
        this.portletWorkerFactory = portletWorkerFactory;
    }

    @Autowired
    public void setEventCoordinationService(IPortletEventCoordinationService eventCoordinationService) {
        this.eventCoordinationService = eventCoordinationService;
    }

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

    @Autowired
    public void setPortletExecutionEventFactory(IPortletExecutionEventFactory portletExecutionEventFactory) {
        this.portletExecutionEventFactory = portletExecutionEventFactory;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            Exception ex) throws Exception {

        final Map<IPortletWindowId, IPortletRenderExecutionWorker> portletHeaderRenderingMap = this
                .getPortletHeaderRenderingMap(request);
        for (final IPortletRenderExecutionWorker portletRenderExecutionWorker : portletHeaderRenderingMap
                .values()) {
            checkWorkerCompletion(request, portletRenderExecutionWorker);
        }

        final Map<IPortletWindowId, IPortletRenderExecutionWorker> portletRenderingMap = this
                .getPortletRenderingMap(request);
        for (final IPortletRenderExecutionWorker portletRenderExecutionWorker : portletRenderingMap.values()) {
            checkWorkerCompletion(request, portletRenderExecutionWorker);
        }
    }

    /**
     * Checks to see if a worker has been retrieved (not orphaned) and if it is complete.
     */
    protected void checkWorkerCompletion(HttpServletRequest request,
            IPortletRenderExecutionWorker portletRenderExecutionWorker) {
        if (!portletRenderExecutionWorker.isRetrieved()) {
            final IPortletWindowId portletWindowId = portletRenderExecutionWorker.getPortletWindowId();
            final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(request,
                    portletWindowId);
            this.logger.warn("Portlet worker started but never retrieved for {}, worker {}."
                    + " If random portlet fnames it may be users switching tabs before page is done rendering"
                    + " (would see separate log message with java.net.SocketException on socket write)."
                    + " If repeatedly occurring with one portlet fname your theme layout xsl may not be including"
                    + " a portlet present in your layout xml files (see"
                    + " http://jasig.275507.n4.nabble.com/Portlet-worker-started-but-never-retrieved-td4580698.html)",
                    portletWindow, portletRenderExecutionWorker);

            try {
                portletRenderExecutionWorker.get(0);
            } catch (Exception e) {
                //Ignore exception here, we just want to get this worker to complete
            }
        }

        if (!portletRenderExecutionWorker.isComplete()) {
            cancelWorker(request, portletRenderExecutionWorker);
        }
    }

    /**
     * Cancel the worker and add it to the hung workers queue
     */
    protected void cancelWorker(HttpServletRequest request, IPortletExecutionWorker<?> portletExecutionWorker) {
        final IPortletWindowId portletWindowId = portletExecutionWorker.getPortletWindowId();
        final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(request, portletWindowId);
        this.logger.warn("{} has not completed, adding to hung-worker cleanup queue: {}", portletExecutionWorker,
                portletWindow);

        portletExecutionWorker.cancel();

        this.portletExecutionEventFactory.publishPortletHungEvent(request, this, portletExecutionWorker);
        hungWorkers.offer(portletExecutionWorker);
    }

    @Scheduled(fixedRate = 1000)
    public void cleanupHungWorkers() {
        if (this.hungWorkers.isEmpty()) {
            return;
        }

        for (final Iterator<IPortletExecutionWorker<?>> workerItr = this.hungWorkers.iterator(); workerItr
                .hasNext();) {
            final IPortletExecutionWorker<?> worker = workerItr.next();

            //If the worker completed remove it from queue
            if (worker.isComplete()) {
                workerItr.remove();
                this.logger.debug("{} has completed and is removed from the hung worker queue after {} cancels",
                        worker, worker.getCancelCount());

                this.portletExecutionEventFactory.publishPortletHungCompleteEvent(this, worker);
            }
            //If the worker is still running cancel it
            else {
                //Log a warning about the worker once every 30 seconds or so
                final int cancelCount = worker.getCancelCount();
                if (cancelCount % 150 == 0) {
                    this.logger.warn("{} is still hung, cancel has been called {} times", worker, cancelCount);
                } else {
                    this.logger.debug("{} is still hung, cancel has been called {} times", worker, cancelCount);
                }

                worker.cancel();
            }
        }

    }

    @Override
    public void preSubmit(HttpServletRequest request, HttpServletResponse response,
            IPortletExecutionContext context) {
    }

    @Override
    public void preExecution(HttpServletRequest request, HttpServletResponse response,
            IPortletExecutionContext context) {
    }

    @Override
    public void postExecution(HttpServletRequest request, HttpServletResponse response,
            IPortletExecutionContext context, Exception e) {
        final IPortletWindowId portletWindowId = context.getPortletWindowId();
        final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(request, portletWindowId);
        final IPortletEntity portletEntity = portletWindow.getPortletEntity();
        final IPortletDefinition portletDefinition = portletEntity.getPortletDefinition();
        final IPortletDescriptorKey portletDescriptorKey = portletDefinition.getPortletDescriptorKey();

        final AtomicInteger counter = this.executionCount.get(portletDescriptorKey);
        counter.incrementAndGet();
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.rendering.IPortletExecutionManager#doPortletAction(org.apereo.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void doPortletAction(IPortletWindowId portletWindowId, HttpServletRequest request,
            HttpServletResponse response) {
        final long timeout = getPortletActionTimeout(portletWindowId, request);

        final IPortletExecutionWorker<Long> portletActionExecutionWorker = this.portletWorkerFactory
                .createActionWorker(request, response, portletWindowId);
        portletActionExecutionWorker.submit();

        try {
            portletActionExecutionWorker.get(timeout);
        } catch (Exception e) {
            // put the exception into the error map for the session
            final Map<IPortletWindowId, Exception> portletFailureMap = getPortletErrorMap(request);
            portletFailureMap.put(portletWindowId, e);
        }

        //If the worker is still running add it to the hung-workers queue
        if (!portletActionExecutionWorker.isComplete()) {
            cancelWorker(request, portletActionExecutionWorker);
        }

        // Is this portlet permitted to emit events?  (Or is it disablePortletEvents=true?)
        final IPortletWindow portletWindow = portletWindowRegistry.getPortletWindow(request, portletWindowId);
        IPortletDefinition portletDefinition = portletWindow.getPortletEntity().getPortletDefinition();
        IPortletDefinitionParameter disablePortletEvents = portletDefinition
                .getParameter(DISABLE_PORTLET_EVENTS_PARAMETER);
        if (disablePortletEvents != null && Boolean.parseBoolean(disablePortletEvents.getValue())) {
            logger.info("Ignoring portlet events for portlet '{}' because they have been disabled.",
                    portletDefinition.getFName());
        } else {
            // Proceed with events...
            final PortletEventQueue portletEventQueue = this.eventCoordinationService.getPortletEventQueue(request);
            this.doPortletEvents(portletEventQueue, request, response);
        }

    }

    public void doPortletEvents(PortletEventQueue eventQueue, HttpServletRequest request,
            HttpServletResponse response) {
        if (eventQueue.getUnresolvedEvents().isEmpty()) {
            return;
        }

        final Map<IPortletWindowId, IPortletExecutionWorker<Long>> eventWorkers = new LinkedHashMap<IPortletWindowId, IPortletExecutionWorker<Long>>();

        //TODO what to do if we hit the max iterations?
        int iteration = 0;
        for (; iteration < this.maxEventIterations; iteration++) {
            //Make sure all queued events have been resolved
            this.eventCoordinationService.resolvePortletEvents(request, eventQueue);

            //Create and submit an event worker for each window with a queued event
            for (final IPortletWindowId eventWindowId : eventQueue) {
                if (eventWorkers.containsKey(eventWindowId)) {
                    /* 
                     * PLT.15.2.5 says that event processing per window must be serialized, if there
                     * is already a working in the map for the window ID skip it for now. we'll get back to it eventually
                     */
                    continue;
                }

                final QueuedEvent queuedEvent = eventQueue.pollEvent(eventWindowId);

                if (queuedEvent != null) {
                    final Event event = queuedEvent.getEvent();
                    final IPortletExecutionWorker<Long> portletEventExecutionWorker = this.portletWorkerFactory
                            .createEventWorker(request, response, eventWindowId, event);
                    eventWorkers.put(eventWindowId, portletEventExecutionWorker);
                    portletEventExecutionWorker.submit();
                }
            }

            //If no event workers exist we're done with event processing!
            if (eventWorkers.isEmpty()) {
                return;
            }

            //See if any of the events have completed
            int completedEventWorkers = 0;
            final Set<Entry<IPortletWindowId, IPortletExecutionWorker<Long>>> entrySet = eventWorkers.entrySet();
            for (final Iterator<Entry<IPortletWindowId, IPortletExecutionWorker<Long>>> eventWorkerEntryItr = entrySet
                    .iterator(); eventWorkerEntryItr.hasNext();) {
                final Entry<IPortletWindowId, IPortletExecutionWorker<Long>> eventWorkerEntry = eventWorkerEntryItr
                        .next();

                final IPortletExecutionWorker<Long> eventWorker = eventWorkerEntry.getValue();
                if (eventWorker.isComplete()) {
                    final IPortletWindowId portletWindowId = eventWorkerEntry.getKey();
                    //TODO return number of new queued events, use to break the loop earlier
                    waitForEventWorker(request, eventQueue, eventWorker, portletWindowId);

                    eventWorkerEntryItr.remove();
                    completedEventWorkers++;
                }
            }

            /*
             * If no event workers have completed without waiting wait for the first one and then loop again
             * Not waiting for all events since each event may spawn more events and we want to start them
             * processing as soon as possible
             */
            if (completedEventWorkers == 0) {
                final Iterator<Entry<IPortletWindowId, IPortletExecutionWorker<Long>>> eventWorkerEntryItr = entrySet
                        .iterator();
                final Entry<IPortletWindowId, IPortletExecutionWorker<Long>> eventWorkerEntry = eventWorkerEntryItr
                        .next();
                eventWorkerEntryItr.remove();

                final IPortletWindowId portletWindowId = eventWorkerEntry.getKey();
                final IPortletExecutionWorker<Long> eventWorker = eventWorkerEntry.getValue();
                waitForEventWorker(request, eventQueue, eventWorker, portletWindowId);
            }
        }

        if (iteration == this.maxEventIterations) {
            this.logger.error("The Event dispatching iteration maximum of " + this.maxEventIterations
                    + " was hit, consider either raising this limit or reviewing the portlets that use events to reduce the number of events spawned");
        }
    }

    protected void waitForEventWorker(HttpServletRequest request, PortletEventQueue eventQueue,
            IPortletExecutionWorker<Long> eventWorker, IPortletWindowId portletWindowId) {

        final long timeout = getPortletEventTimeout(portletWindowId, request);

        try {
            eventWorker.get(timeout);
        } catch (Exception e) {
            // put the exception into the error map for the session
            //TODO event error handling?
            final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(request,
                    portletWindowId);
            logger.warn(portletWindow
                    + " threw an execption while executing an event. This chain of event handling will terminate.",
                    e);
        }

        //If the worker is still running add it to the hung-workers queue
        if (!eventWorker.isComplete()) {
            cancelWorker(request, eventWorker);
        }
    }

    /**
     * Only actually starts rendering the head if the portlet has the 'javax.portlet.renderHeaders' container-runtime-option
     * present and set to "true."
     * 
    * @see IPortletExecutionManager#startPortletHeaderRender(org.apereo.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
    */
    @Override
    public void startPortletHeaderRender(IPortletWindowId portletWindowId, HttpServletRequest request,
            HttpServletResponse response) {
        if (doesPortletNeedHeaderWorker(portletWindowId, request)) {
            this.startPortletHeaderRenderInternal(portletWindowId, request, response);
        } else {
            this.logger.debug(
                    "ignoring startPortletHeadRender request since containerRuntimeOption is not present for portletWindowId "
                            + portletWindowId);
        }
    }

    /**
     * 
     * @param portletWindowId
     * @param request
     * @return
     */
    protected boolean doesPortletNeedHeaderWorker(IPortletWindowId portletWindowId, HttpServletRequest request) {
        IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(request, portletWindowId);
        PortletDefinition portletDefinition = portletWindow.getPlutoPortletWindow().getPortletDefinition();
        ContainerRuntimeOption renderHeaderOption = portletDefinition
                .getContainerRuntimeOption(PORTLET_RENDER_HEADERS_OPTION);
        boolean result = false;
        if (renderHeaderOption != null) {
            result = renderHeaderOption.getValues().contains(Boolean.TRUE.toString());
        }
        logger.debug("Portlet {} need render header worker: {}", portletDefinition.getPortletName(), result);
        return result;
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.rendering.IPortletExecutionManager#startPortletRender(org.apereo.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void startPortletRender(IPortletWindowId portletWindowId, HttpServletRequest request,
            HttpServletResponse response) {
        this.startPortletRenderInternal(portletWindowId, request, response);
    }

    /* (non-Javadoc)
    * @see org.apereo.portal.portlet.rendering.IPortletExecutionManager#serveResource(org.apereo.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
    */
    @Override
    public void doPortletServeResource(IPortletWindowId portletWindowId, HttpServletRequest request,
            HttpServletResponse response) {
        final long timeout = getPortletResourceTimeout(portletWindowId, request);

        final IPortletExecutionWorker<Long> resourceWorker = this.portletWorkerFactory.createResourceWorker(request,
                response, portletWindowId);
        resourceWorker.submit();

        try {
            resourceWorker.get(timeout);
        } catch (Exception e) {
            // Log the exception but not this thread's stacktrace. The portlet worker has already logged its stack trace
            this.logger.error("resource worker {} failed with exception {}", resourceWorker, e.toString());
            // render generic serveResource error
            try {
                if (!response.isCommitted()) {
                    response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "resource unavailable");
                }
            } catch (IOException e1) {
                logger.error("caught IOException trying to send error response for failed resource worker", e);
            }
        }

        //If the worker is still running add it to the hung-workers queue
        if (!resourceWorker.isComplete()) {
            cancelWorker(request, resourceWorker);
        }
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.rendering.IPortletExecutionManager#isPortletHeaderRenderRequested(org.apereo.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public boolean isPortletRenderHeaderRequested(IPortletWindowId portletWindowId, HttpServletRequest request,
            HttpServletResponse response) {
        final Map<IPortletWindowId, IPortletRenderExecutionWorker> portletRenderingMap = this
                .getPortletHeaderRenderingMap(request);
        final IPortletRenderExecutionWorker tracker = portletRenderingMap.get(portletWindowId);

        return tracker != null;
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.rendering.IPortletExecutionManager#isPortletRenderRequested(org.apereo.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public boolean isPortletRenderRequested(IPortletWindowId portletWindowId, HttpServletRequest request,
            HttpServletResponse response) {
        final Map<IPortletWindowId, IPortletRenderExecutionWorker> portletRenderingMap = this
                .getPortletRenderingMap(request);
        final IPortletRenderExecutionWorker tracker = portletRenderingMap.get(portletWindowId);

        return tracker != null;
    }

    /* (non-Javadoc)
    * @see org.apereo.portal.portlet.rendering.IPortletExecutionManager#getPortletHeadOutput(org.apereo.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
    */
    @Override
    public String getPortletHeadOutput(IPortletWindowId portletWindowId, HttpServletRequest request,
            HttpServletResponse response) {
        if (doesPortletNeedHeaderWorker(portletWindowId, request)) {
            final IPortletRenderExecutionWorker tracker = getRenderedPortletHeaderWorker(portletWindowId, request,
                    response);
            final long timeout = getPortletRenderTimeout(portletWindowId, request);
            try {
                final String output = tracker.getOutput(timeout);
                return output == null ? "" : output;
            } catch (Exception e) {
                logger.error("failed to render header output for " + portletWindowId, e);
                return "";
            }
        }

        logger.debug(portletWindowId + " does not produce output for header");
        return "";
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.rendering.IPortletExecutionManager#getPortletOutput(org.apereo.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public String getPortletOutput(IPortletWindowId portletWindowId, HttpServletRequest request,
            HttpServletResponse response) {
        final IPortletRenderExecutionWorker tracker = getRenderedPortletBodyWorker(portletWindowId, request,
                response);
        final long timeout = getPortletRenderTimeout(portletWindowId, request);

        try {
            final String output = tracker.getOutput(timeout);
            return output == null ? "" : output;
        } catch (Exception e) {
            final IPortletFailureExecutionWorker failureWorker = this.portletWorkerFactory
                    .createFailureWorker(request, response, portletWindowId, e);
            // TODO publish portlet error event?
            try {
                failureWorker.submit();
                return failureWorker.getOutput(timeout);
            } catch (Exception e1) {
                logger.error("Failed to render error portlet for: " + portletWindowId, e1);
                return "Error Portlet Unavailable. Please contact your portal administrators.";
            }
        }
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.rendering.IPortletExecutionManager#getPortletTitle(org.apereo.portal.portlet.om.IPortletWindowId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public String getPortletTitle(IPortletWindowId portletWindowId, HttpServletRequest request,
            HttpServletResponse response) {
        final IPortletDefinition portletDefinition = getPortletDefinition(portletWindowId, request);
        final IPortletDefinitionParameter disableDynamicTitle = portletDefinition
                .getParameter("disableDynamicTitle");

        if (disableDynamicTitle == null || !Boolean.parseBoolean(disableDynamicTitle.getValue())) {
            try {
                final PortletRenderResult portletRenderResult = getPortletRenderResult(portletWindowId, request,
                        response);
                if (portletRenderResult != null) {
                    final String title = portletRenderResult.getTitle();
                    if (title != null) {
                        return title;
                    }
                }
            } catch (Exception e) {
                logger.warn(
                        "unable to get portlet title, falling back to title defined in channel definition for portletWindowId "
                                + portletWindowId);
            }
        }

        // we assume that response locale has been set to correct value
        String locale = response.getLocale().toString();

        // return portlet title from channel definition
        return portletDefinition.getTitle(locale);
    }

    @Override
    public int getPortletNewItemCount(IPortletWindowId portletWindowId, HttpServletRequest request,
            HttpServletResponse response) {
        try {
            final PortletRenderResult portletRenderResult = getPortletRenderResult(portletWindowId, request,
                    response);
            if (portletRenderResult != null) {
                final int newItemCount = portletRenderResult.getNewItemCount();
                return newItemCount;
            }
        } catch (Exception e) {
            logger.warn("unable to get portlet new item count for portletWindowId " + portletWindowId);
        }

        return 0;
    }

    @Override
    public String getPortletLink(IPortletWindowId portletWindowId, String defaultPortletUrl,
            HttpServletRequest request, HttpServletResponse response) {
        try {
            final PortletRenderResult portletRenderResult = getPortletRenderResult(portletWindowId, request,
                    response);
            if (portletRenderResult != null) {
                final String link = portletRenderResult.getExternalLink();
                if (StringUtils.isNotBlank(link)) {
                    return link;
                } else {
                    return defaultPortletUrl;
                }
            }
        } catch (Exception e) {
            logger.warn("unable to get portlet link count for portletWindowId " + portletWindowId);
        }

        return defaultPortletUrl;
    }

    /**
     * This method handles portlets that are slow to warm up. The default config multiplies the portlet's
     * configured timeout by 20 the first 5 times it executes. The key is the portlet descriptor so even if you
     * have the same portlet (web proxy for example) published 20 times only the first 5 renders of ANY WPP
     * will get the extra time.
     * @param portletDefinition
     * @param request
     * @param timeout
     * @return
     */
    protected final long getModifiedTimeout(IPortletDefinition portletDefinition, HttpServletRequest request,
            long timeout) {
        final IPortletDescriptorKey portletDescriptorKey = portletDefinition.getPortletDescriptorKey();
        final AtomicInteger counter = this.executionCount.get(portletDescriptorKey);
        final int executionCount = counter.get();

        if (executionCount > extendedTimeoutExecutions) {
            return timeout;
        }

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Modifying timeout for %40s from %7s to %8s on execution %2s\n",
                    portletDescriptorKey.toString(), timeout, timeout * extendedTimeoutMultiplier, executionCount));
        }
        return timeout * extendedTimeoutMultiplier;
    }

    protected long getPortletActionTimeout(IPortletWindowId portletWindowId, HttpServletRequest request) {
        if (this.ignoreTimeouts) {
            return DEBUG_TIMEOUT;
        }

        final IPortletDefinition portletDefinition = getPortletDefinition(portletWindowId, request);
        final Integer actionTimeout = portletDefinition.getActionTimeout();
        if (actionTimeout != null) {
            return getModifiedTimeout(portletDefinition, request, actionTimeout);
        }

        return getModifiedTimeout(portletDefinition, request, portletDefinition.getTimeout());
    }

    protected long getPortletEventTimeout(IPortletWindowId portletWindowId, HttpServletRequest request) {
        if (this.ignoreTimeouts) {
            return DEBUG_TIMEOUT;
        }

        final IPortletDefinition portletDefinition = getPortletDefinition(portletWindowId, request);
        final Integer eventTimeout = portletDefinition.getEventTimeout();
        if (eventTimeout != null) {
            return getModifiedTimeout(portletDefinition, request, eventTimeout);
        }

        return getModifiedTimeout(portletDefinition, request, portletDefinition.getTimeout());
    }

    protected long getPortletRenderTimeout(IPortletWindowId portletWindowId, HttpServletRequest request) {
        if (this.ignoreTimeouts) {
            return DEBUG_TIMEOUT;
        }

        final IPortletDefinition portletDefinition = getPortletDefinition(portletWindowId, request);
        final Integer renderTimeout = portletDefinition.getRenderTimeout();
        if (renderTimeout != null) {
            return getModifiedTimeout(portletDefinition, request, renderTimeout);
        }

        return getModifiedTimeout(portletDefinition, request, portletDefinition.getTimeout());
    }

    protected long getPortletResourceTimeout(IPortletWindowId portletWindowId, HttpServletRequest request) {
        if (this.ignoreTimeouts) {
            return DEBUG_TIMEOUT;
        }

        final IPortletDefinition portletDefinition = getPortletDefinition(portletWindowId, request);
        final Integer resourceTimeout = portletDefinition.getResourceTimeout();
        if (resourceTimeout != null) {
            return getModifiedTimeout(portletDefinition, request, resourceTimeout);
        }

        return getModifiedTimeout(portletDefinition, request, portletDefinition.getTimeout());
    }

    protected IPortletDefinition getPortletDefinition(IPortletWindowId portletWindowId,
            HttpServletRequest request) {
        final IPortletWindow portletWindow = this.portletWindowRegistry.getPortletWindow(request, portletWindowId);
        final IPortletEntity parentPortletEntity = portletWindow.getPortletEntity();
        return parentPortletEntity.getPortletDefinition();
    }

    protected IPortletRenderExecutionWorker getRenderedPortletHeaderWorker(IPortletWindowId portletWindowId,
            HttpServletRequest request, HttpServletResponse response) {
        final Map<IPortletWindowId, IPortletRenderExecutionWorker> portletHeaderRenderingMap = this
                .getPortletHeaderRenderingMap(request);
        IPortletRenderExecutionWorker portletHeaderRenderWorker = portletHeaderRenderingMap.get(portletWindowId);
        if (portletHeaderRenderWorker == null) {
            portletHeaderRenderWorker = this.startPortletHeaderRenderInternal(portletWindowId, request, response);
        }
        return portletHeaderRenderWorker;
    }

    protected IPortletRenderExecutionWorker getRenderedPortletBodyWorker(IPortletWindowId portletWindowId,
            HttpServletRequest request, HttpServletResponse response) {
        final Map<IPortletWindowId, IPortletRenderExecutionWorker> portletRenderingMap = this
                .getPortletRenderingMap(request);
        IPortletRenderExecutionWorker tracker = portletRenderingMap.get(portletWindowId);
        if (tracker == null) {
            tracker = this.startPortletRenderInternal(portletWindowId, request, response);

        }
        return tracker;
    }

    /**
     * Returns the PortletRenderResult waiting up to the portlet's timeout
     * 
     * @return The PortletRenderResult from the portlet's execution
     * @throws TimeoutException If the portlet's timeout was hit before a result was returned
     * @throws Exception The exception thrown by the portlet during execution 
     */
    protected PortletRenderResult getPortletRenderResult(IPortletWindowId portletWindowId,
            HttpServletRequest request, HttpServletResponse response) throws Exception {
        final IPortletRenderExecutionWorker tracker = getRenderedPortletBodyWorker(portletWindowId, request,
                response);
        final long timeout = getPortletRenderTimeout(portletWindowId, request);
        return tracker.get(timeout);
    }

    /**
     * create and submit the portlet header rendering job to the thread pool
     * 
     * @param portletWindowId
     * @param request
     * @param response
     * @return
     */
    protected IPortletRenderExecutionWorker startPortletHeaderRenderInternal(IPortletWindowId portletWindowId,
            HttpServletRequest request, HttpServletResponse response) {
        IPortletRenderExecutionWorker portletHeaderRenderWorker = this.portletWorkerFactory
                .createRenderHeaderWorker(request, response, portletWindowId);
        portletHeaderRenderWorker.submit();

        final Map<IPortletWindowId, IPortletRenderExecutionWorker> portletHeaderRenderingMap = this
                .getPortletHeaderRenderingMap(request);
        portletHeaderRenderingMap.put(portletWindowId, portletHeaderRenderWorker);

        return portletHeaderRenderWorker;
    }

    /**
     * create and submit the portlet content rendering job to the thread pool
     */
    protected IPortletRenderExecutionWorker startPortletRenderInternal(IPortletWindowId portletWindowId,
            HttpServletRequest request, HttpServletResponse response) {
        // first check to see if there is a Throwable in the session for this IPortletWindowId
        final Map<IPortletWindowId, Exception> portletFailureMap = getPortletErrorMap(request);
        final Exception cause = portletFailureMap.remove(portletWindowId);

        final IPortletRenderExecutionWorker portletRenderExecutionWorker;
        if (null != cause) {
            // previous action failed, dispatch to errorPortlet immediately
            portletRenderExecutionWorker = this.portletWorkerFactory.createFailureWorker(request, response,
                    portletWindowId, cause);
        } else {
            IPortletWindow portletWindow = portletWindowRegistry.getPortletWindow(request, portletWindowId);
            IPortletDefinition portletDef = portletWindow.getPortletEntity().getPortletDefinition();
            if (portletDef.getLifecycleState().equals(PortletLifecycleState.MAINTENANCE)) {
                // Prevent the portlet from rendering;  replace with a helpful "Out of Service" message
                portletRenderExecutionWorker = this.portletWorkerFactory.createFailureWorker(request, response,
                        portletWindowId, new MaintenanceModeException());
            } else {
                // Happy path
                portletRenderExecutionWorker = this.portletWorkerFactory.createRenderWorker(request, response,
                        portletWindowId);
            }
        }

        portletRenderExecutionWorker.submit();

        final Map<IPortletWindowId, IPortletRenderExecutionWorker> portletRenderingMap = this
                .getPortletRenderingMap(request);
        portletRenderingMap.put(portletWindowId, portletRenderExecutionWorker);

        return portletRenderExecutionWorker;
    }

    /**
     * Returns a request attribute scoped Map of portlets that are rendering for the current request.
     */
    @SuppressWarnings("unchecked")
    protected Map<IPortletWindowId, IPortletRenderExecutionWorker> getPortletHeaderRenderingMap(
            HttpServletRequest request) {
        synchronized (PortalWebUtils.getRequestAttributeMutex(request)) {
            Map<IPortletWindowId, IPortletRenderExecutionWorker> portletRenderingMap = (Map<IPortletWindowId, IPortletRenderExecutionWorker>) request
                    .getAttribute(PORTLET_HEADER_RENDERING_MAP);
            if (portletRenderingMap == null) {
                portletRenderingMap = new ConcurrentHashMap<IPortletWindowId, IPortletRenderExecutionWorker>();
                request.setAttribute(PORTLET_HEADER_RENDERING_MAP, portletRenderingMap);
            }
            return portletRenderingMap;
        }
    }

    /**
     * Returns a request attribute scoped Map of portlets that are rendering for the current request.
     */
    @SuppressWarnings("unchecked")
    protected Map<IPortletWindowId, IPortletRenderExecutionWorker> getPortletRenderingMap(
            HttpServletRequest request) {
        synchronized (PortalWebUtils.getRequestAttributeMutex(request)) {
            Map<IPortletWindowId, IPortletRenderExecutionWorker> portletRenderingMap = (Map<IPortletWindowId, IPortletRenderExecutionWorker>) request
                    .getAttribute(PORTLET_RENDERING_MAP);
            if (portletRenderingMap == null) {
                portletRenderingMap = new ConcurrentHashMap<IPortletWindowId, IPortletRenderExecutionWorker>();
                request.setAttribute(PORTLET_RENDERING_MAP, portletRenderingMap);
            }
            return portletRenderingMap;
        }
    }

    /**
     * Null safe means for retrieving the {@link Map} from the specified session
     * keyed by {@link #SESSION_ATTRIBUTE__PORTLET_FAILURE_CAUSE_MAP}.
     * 
     * @param request HttpServletRequest
     * @return a never null {@link Map} in the session for storing portlet failure causes.
     */
    @SuppressWarnings("unchecked")
    protected Map<IPortletWindowId, Exception> getPortletErrorMap(HttpServletRequest request) {
        final HttpSession session = request.getSession();
        synchronized (WebUtils.getSessionMutex(session)) {
            Map<IPortletWindowId, Exception> portletFailureMap = (Map<IPortletWindowId, Exception>) session
                    .getAttribute(SESSION_ATTRIBUTE__PORTLET_FAILURE_CAUSE_MAP);
            if (portletFailureMap == null) {
                portletFailureMap = new ConcurrentHashMap<IPortletWindowId, Exception>();
                session.setAttribute(SESSION_ATTRIBUTE__PORTLET_FAILURE_CAUSE_MAP, portletFailureMap);
            }
            return portletFailureMap;
        }

    }

}