/** * 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: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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 com.google.common.base.Function; 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 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. * */ @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; } } }