Package org.jasig.portal.portlet.rendering

Source Code of org.jasig.portal.portlet.rendering.PortletExecutionManager

/**
* 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.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.jasig.portal.events.IPortletExecutionEventFactory;
import org.jasig.portal.portlet.om.IPortletDefinition;
import org.jasig.portal.portlet.om.IPortletDefinitionParameter;
import org.jasig.portal.portlet.om.IPortletDescriptorKey;
import org.jasig.portal.portlet.om.IPortletEntity;
import org.jasig.portal.portlet.om.IPortletEntityId;
import org.jasig.portal.portlet.om.IPortletWindow;
import org.jasig.portal.portlet.om.IPortletWindowId;
import org.jasig.portal.portlet.om.PortletLifecycleState;
import org.jasig.portal.portlet.registry.IPortletWindowRegistry;
import org.jasig.portal.portlet.rendering.worker.IPortletExecutionContext;
import org.jasig.portal.portlet.rendering.worker.IPortletExecutionInterceptor;
import org.jasig.portal.portlet.rendering.worker.IPortletExecutionWorker;
import org.jasig.portal.portlet.rendering.worker.IPortletFailureExecutionWorker;
import org.jasig.portal.portlet.rendering.worker.IPortletRenderExecutionWorker;
import org.jasig.portal.portlet.rendering.worker.IPortletWorkerFactory;
import org.jasig.portal.portlets.error.MaintenanceModeException;
import org.jasig.portal.utils.ConcurrentMapUtils;
import org.jasig.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.util.Assert;
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
* @version $Revision$
*/
@ManagedResource("uPortal:section=Framework,name=PortletExecutionManager")
@Service("portletExecutionManager")
public class PortletExecutionManager extends HandlerInterceptorAdapter
        implements IPortletExecutionManager, IPortletExecutionInterceptor, PortletExecutionManagerMXBean {
   
    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.jasig.portal.portlet.maxEventIterations:100}")
    public void setMaxEventIterations(int maxEventIterations) {
        this.maxEventIterations = maxEventIterations;
    }
   
    @Override
    public int getMaxEventIterations() {
        return this.maxEventIterations;
    }

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

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

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

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

    @Override
    @Value("${org.jasig.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.jasig.portal.portlet.rendering.IPortletExecutionManager#doPortletAction(org.jasig.portal.portlet.om.IPortletEntityId, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void doPortletAction(IPortletEntityId portletEntityId, HttpServletRequest request, HttpServletResponse response) {
        final IPortletWindow portletWindow = this.portletWindowRegistry.getOrCreateDefaultPortletWindow(request, portletEntityId);
        this.doPortletAction(portletWindow.getPortletWindowId(), request, response);
    }

    /* (non-Javadoc)
     * @see org.jasig.portal.portlet.rendering.IPortletExecutionManager#doPortletAction(org.jasig.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);
        }
   
    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);
        }
    }
   
   
   
    /* (non-Javadoc)
   * @see org.jasig.portal.portlet.rendering.IPortletExecutionManager#startPortletHeadRender(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
   */
  @Override
  public void startPortletHeaderRender(String subscribeId,
      HttpServletRequest request, HttpServletResponse response) {
    Assert.notNull(subscribeId, "subscribeId cannot be null");
       
        final IPortletWindow portletWindow = this.portletWindowRegistry.getOrCreateDefaultPortletWindowByLayoutNodeId(request, subscribeId);
       
        if(portletWindow != null) {
          this.startPortletHeaderRender(portletWindow.getPortletWindowId(), request, response);
        } else {
          this.logger.debug("ignoring startPortletHeadRender since getDefaultPortletWindow returned null for subscribeId " + subscribeId);
        }
       
  }
    /**
     * Only actually starts rendering the head if the portlet has the 'javax.portlet.renderHeaders' container-runtime-option
     * present and set to "true."
     *
   * @see org.jasig.portal.portlet.rendering.IPortletExecutionManager#startPortletHeaderRender(org.jasig.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.jasig.portal.portlet.rendering.IPortletExecutionManager#startPortletRender(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void startPortletRender(String subscribeId, HttpServletRequest request, HttpServletResponse response) {
        Assert.notNull(subscribeId, "subscribeId cannot be null");
       
        final IPortletWindow portletWindow = this.portletWindowRegistry.getOrCreateDefaultPortletWindowByLayoutNodeId(request, subscribeId);
       
        if(null != portletWindow) {
          this.startPortletRenderInternal(portletWindow.getPortletWindowId(), request, response);
        } else {
          this.logger.debug("skipping startPortletRender due to null result from getDefaultPortletWindow for subscribeId " + subscribeId);
        }
    }
   
    /* (non-Javadoc)
     * @see org.jasig.portal.portlet.rendering.IPortletExecutionManager#startPortletRender(org.jasig.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.jasig.portal.portlet.rendering.IPortletExecutionManager#serveResource(org.jasig.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.jasig.portal.portlet.rendering.IPortletExecutionManager#isPortletHeaderRenderRequested(org.jasig.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.jasig.portal.portlet.rendering.IPortletExecutionManager#isPortletHeaderRenderRequested(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
   */
  @Override
  public boolean isPortletRenderHeaderRequested(String subscribeId,
      HttpServletRequest request, HttpServletResponse response) {
      final IPortletWindow portletWindow = this.portletWindowRegistry.getOrCreateDefaultPortletWindowByLayoutNodeId(request, subscribeId);

    if(portletWindow == null) {
      this.logger.debug("returning false since getDefaultPortletWindow returned null for subscribeId " + subscribeId);
      return false;
    }
    return this.isPortletRenderHeaderRequested(portletWindow.getPortletWindowId(), request, response);
  }

  /* (non-Javadoc)
     * @see org.jasig.portal.portlet.rendering.IPortletExecutionManager#isPortletRenderRequested(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public boolean isPortletRenderRequested(String subscribeId, HttpServletRequest request, HttpServletResponse response) {
        final IPortletWindow portletWindow = this.portletWindowRegistry.getOrCreateDefaultPortletWindowByLayoutNodeId(request, subscribeId);
       
        if (portletWindow == null) {
          this.logger.warn("returning false for isPortletRenderRequested due to null result for getDefaultPortletWindow on subscribeId " + subscribeId);
          return false;
        }
       
        final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();
        return this.isPortletRenderRequested(portletWindowId, request, response);
       
    }
   
    /* (non-Javadoc)
     * @see org.jasig.portal.portlet.rendering.IPortletExecutionManager#isPortletRenderRequested(org.jasig.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.jasig.portal.portlet.rendering.IPortletExecutionManager#getPortletHeadOutput(org.jasig.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.jasig.portal.portlet.rendering.IPortletExecutionManager#getPortletHeadOutput(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
   */
  @Override
  public String getPortletHeadOutput(String subscribeId,
      HttpServletRequest request, HttpServletResponse response) {
      final IPortletWindow portletWindow = this.portletWindowRegistry.getOrCreateDefaultPortletWindowByLayoutNodeId(request, subscribeId);
   
    if (portletWindow == null) {
        this.logger.warn("Could not find portlet window for layout node id, empty header content will be returned: " + subscribeId);
            return "";
    }

    final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();

    return this.getPortletHeadOutput(portletWindowId, request, response);
  }

  /* (non-Javadoc)
     * @see org.jasig.portal.portlet.rendering.IPortletExecutionManager#getPortletOutput(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public String getPortletOutput(String subscribeId, HttpServletRequest request, HttpServletResponse response) {
            Assert.notNull(subscribeId, "subscribeId cannot be null");
       
        final IPortletWindow portletWindow = this.portletWindowRegistry.getOrCreateDefaultPortletWindowByLayoutNodeId(request, subscribeId);
       
        if (portletWindow == null) {
            this.logger.warn("Could not find portlet window for layout node id, empty missing content text will be returned: " + subscribeId);
            return "This portlet does not exist or is not deployed correctly.";
        }

        final IPortletWindowId portletWindowId = portletWindow.getPortletWindowId();
        return this.getPortletOutput(portletWindowId, request, response);
    }
   
    /* (non-Javadoc)
     * @see org.jasig.portal.portlet.rendering.IPortletExecutionManager#getPortletOutput(org.jasig.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.";
            }
    }
    }

    @Override
    public long getPortletRenderTime(IPortletWindowId portletWindowId, HttpServletRequest request, HttpServletResponse response) {
        try {
            final PortletRenderResult portletRenderResult = getPortletRenderResult(portletWindowId, request, response);
            return portletRenderResult.getRenderTime();
        } catch (Exception e) {
            logger.warn("unable to get portlet render time, returning -1 " + portletWindowId);
        }
       
        return -1;
    }

    /* (non-Javadoc)
     * @see org.jasig.portal.portlet.rendering.IPortletExecutionManager#getPortletTitle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public String getPortletTitle(String subscribeId, HttpServletRequest request, HttpServletResponse response) {
        Assert.notNull(subscribeId, "subscribeId cannot be null");
       
        final IPortletWindow portletWindow = this.portletWindowRegistry.getOrCreateDefaultPortletWindowByLayoutNodeId(request, subscribeId);
       
        if (portletWindow == null) {
            this.logger.warn("Could not find portlet window for layout node id, empty title content will be returned: " + subscribeId);
            return "";
        }

        return this.getPortletTitle(portletWindow.getPortletWindowId(), request, response);
    }
   
    /* (non-Javadoc)
     * @see org.jasig.portal.portlet.rendering.IPortletExecutionManager#getPortletTitle(org.jasig.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(String subscribeId, HttpServletRequest request, HttpServletResponse response) {
        Assert.notNull(subscribeId, "subscribeId cannot be null");
       
        final IPortletWindow portletWindow = this.portletWindowRegistry.getOrCreateDefaultPortletWindowByLayoutNodeId(request, subscribeId);
        if (portletWindow == null) {
            return 0;
        }

        return this.getPortletNewItemCount(portletWindow.getPortletWindowId(), request, response);
    }

    @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;
        }
       
    }
   
}
TOP

Related Classes of org.jasig.portal.portlet.rendering.PortletExecutionManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.