/** {@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);
    }
  }
  /**
   * Paints the targeted ajax regions. The format of the response is an agreement between the server
   * and the client side JavaScript handling our ajax response.
   *
   * @param renderContext the renderContext to send the output to.
   */
  @Override
  public void paint(final RenderContext renderContext) {
    AjaxOperation operation = AjaxHelper.getCurrentOperation();
    if (operation == null) {
      // the request attribute that we place in the ui context in the action phase can't be null
      throw new SystemException(
          "Can't paint AJAX response. Couldn't find the expected reference to the AjaxOperation.");
    }

    if (operation.getTargetContainerId() != null) {
      paintContainerResponse(renderContext, operation);
    } else {
      paintResponse(renderContext, operation);
    }
  }
  /**
   * 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");
  }