/**
   * Check if process this trigger only.
   *
   * @param triggerWithContext the trigger with its context
   * @param operation current ajax operation
   * @return true if process this trigger only
   */
  private boolean isProcessTriggerOnly(
      final ComponentWithContext triggerWithContext, final AjaxOperation operation) {
    // Target container implies only process the trigger
    if (operation.getTargetContainerId() != null) {
      return true;
    }

    WComponent trigger = triggerWithContext.getComponent();

    // Check if trigger is a polling AJAX control
    if (trigger instanceof WAjaxControl) {
      // Get user context
      UIContext uic = triggerWithContext.getContext();
      UIContextHolder.pushContext(uic);
      try {
        WAjaxControl ajax = (WAjaxControl) trigger;
        // Is a polling region so only process trigger
        if (ajax.getDelay() > 0) {
          return true;
        }
      } finally {
        UIContextHolder.popContext();
      }
    }

    // Check if the operation only has one target and it is the same as the trigger
    List<String> targets = operation.getTargets();
    if (targets == null || targets.isEmpty() || targets.size() > 1) {
      return false;
    }
    return operation.getTriggerId().equals(targets.get(0));
  }
  /**
   * Convenience method that adds a parameter emulating a button press.
   *
   * @param uic the current user's UIContext
   * @param button the button to add a parameter for.
   */
  public void addParameterForButton(final UIContext uic, final WButton button) {
    UIContextHolder.pushContext(uic);

    try {
      parameters.put(button.getId(), "x");
    } finally {
      UIContextHolder.popContext();
    }
  }
  /** {@inheritDoc} */
  @Override
  public void serviceRequest(final Request request) {
    String triggerId = request.getParameter(WServlet.AJAX_TRIGGER_PARAM_NAME);

    AjaxOperation ajaxOperation = AjaxHelper.getCurrentOperation();
    if (ajaxOperation == null) {
      throw new IllegalStateException("No AJAX operation available for trigger " + triggerId + ".");
    }

    ComponentWithContext triggerWithContext = AjaxHelper.getCurrentTriggerAndContext();
    if (triggerWithContext == null) {
      throw new IllegalStateException(
          "No component/context available for AJAX trigger " + triggerId + ".");
    }

    UIContext uic = UIContextHolder.getCurrent();

    // Reset the focus for this new request.
    uic.setFocussed(null, null);

    // We've hit the action phase, so we do want focus on this app.
    uic.setFocusRequired(true);

    // Process trigger only
    if (isProcessTriggerOnly(triggerWithContext, ajaxOperation)) {
      // Get user context
      UIContext tuic = triggerWithContext.getContext();
      UIContextHolder.pushContext(tuic);
      try {
        WComponent trigger = triggerWithContext.getComponent();
        trigger.serviceRequest(request);
        // Manually invoke laters as the InvokeLaters in the service request is not run due to the
        // trigger
        // having a "parent"
        tuic.doInvokeLaters();
      } finally {
        UIContextHolder.popContext();
      }
    }
    // GET only supports the above scenarios
    else if ("GET".equals(request.getMethod())) {
      throw new IllegalStateException(
          "GET is not supported for the AJAX trigger " + triggerId + ".");
    } else {
      // service the request
      super.serviceRequest(request);
    }
  }
  /**
   * Retrieves the application title for the given context. This mess is required due to WWindow
   * essentially existing as a separate UI root. If WWindow is eventually removed, then we can just
   * retrieve the title from the root WApplication.
   *
   * @param uic the context to check.
   * @return the current application title.
   */
  private String getApplicationTitle(final UIContext uic) {
    WComponent root = uic.getUI();
    String title = root instanceof WApplication ? ((WApplication) root).getTitle() : null;

    Map<String, String> params = uic.getEnvironment().getHiddenParameters();
    String target = params.get(WWindow.WWINDOW_REQUEST_PARAM_KEY);

    if (target != null) {
      ComponentWithContext targetComp = WebUtilities.getComponentById(target, true);

      if (targetComp != null && targetComp.getComponent() instanceof WWindow) {
        try {
          UIContextHolder.pushContext(targetComp.getContext());
          title = ((WWindow) targetComp.getComponent()).getTitle();
        } finally {
          UIContextHolder.popContext();
        }
      }
    }

    return title == null ? "" : title;
  }
  /**
   * Paint the ajax container response.
   *
   * @param renderContext the render context
   * @param operation the ajax operation
   */
  private void paintContainerResponse(
      final RenderContext renderContext, final AjaxOperation operation) {
    WebXmlRenderContext webRenderContext = (WebXmlRenderContext) renderContext;
    XmlStringBuilder xml = webRenderContext.getWriter();

    // Get trigger's context
    ComponentWithContext trigger = AjaxHelper.getCurrentTriggerAndContext();
    if (trigger == null) {
      throw new SystemException("No context available for trigger " + operation.getTriggerId());
    }

    xml.appendTagOpen("ui:ajaxTarget");
    xml.appendAttribute("id", operation.getTargetContainerId());
    xml.appendAttribute("action", "replaceContent");
    xml.appendClose();

    // Paint targets - Assume targets are in the same context as the trigger
    UIContextHolder.pushContext(trigger.getContext());
    try {
      for (String targetId : operation.getTargets()) {
        ComponentWithContext target = null;
        if (targetId.equals(operation.getTriggerId())) {
          target = trigger;
        } else {
          target = WebUtilities.getComponentById(targetId, true);
          if (target == null) {
            log.warn("Could not find ajax target to render " + target);
            continue;
          }
        }
        target.getComponent().paint(renderContext);
      }
    } finally {
      UIContextHolder.popContext();
    }

    xml.appendEndTag("ui:ajaxTarget");
  }