/* (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);
    }
  }
  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);
    }
  }
  /** 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);
  }
  /* (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);
    }
  }
  @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();
      }
    }
  }
  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");
    }
  }