/**
  * Respond to a range request, either with the requested bytes, or with a {@code 416 REQUESTED
  * RANGE NOT SATISFIABLE} response if the requested byte range falls outside the length of the
  * attachment. If the range request header is syntactically invalid, nothing is written, and
  * instead {@code false} is returned, letting the action handler ignore the Range header and treat
  * this as a normal (full) download request.
  *
  * @param attachment the attachment to get content from
  * @param request the current client request
  * @param response the response to write to.
  * @param context the current request context
  * @return {@code true} if the partial content request was syntactically valid and a response was
  *     sent, {@code false} otherwise
  * @throws XWikiException if the attachment content cannot be retrieved
  * @throws IOException if the response cannot be written
  */
 private static boolean sendPartialContent(
     final XWikiAttachment attachment,
     final XWikiRequest request,
     final XWikiResponse response,
     final XWikiContext context)
     throws XWikiException, IOException {
   String range = request.getHeader(RANGE_HEADER_NAME);
   Matcher m = RANGE_HEADER_PATTERN.matcher(range);
   if (m.matches()) {
     String startStr = m.group(1);
     String endStr = m.group(2);
     Long start = NumberUtils.createLong(startStr);
     Long end = NumberUtils.createLong(endStr);
     if (start == null && end != null && end > 0) {
       // Tail request, output the last <end> bytes
       start = Math.max(attachment.getContentSize(context) - end, 0L);
       end = attachment.getContentSize(context) - 1L;
     }
     if (!isValidRange(start, end)) {
       return false;
     }
     if (end == null) {
       end = attachment.getContentSize(context) - 1L;
     }
     end = Math.min(end, attachment.getContentSize(context) - 1L);
     writeByteRange(attachment, start, end, request, response, context);
     return true;
   }
   return false;
 }
  @Override
  public String render(XWikiContext context) throws XWikiException {
    XWikiRequest request = context.getRequest();
    XWikiResponse response = context.getResponse();
    XWikiDocument doc = context.getDoc();
    String path = request.getRequestURI();
    String filename = Util.decodeURI(getFileName(path, ACTION_NAME), context);
    XWikiAttachment attachment = null;

    final String idStr = request.getParameter("id");
    if (StringUtils.isNumeric(idStr)) {
      int id = Integer.parseInt(idStr);
      if (doc.getAttachmentList().size() > id) {
        attachment = doc.getAttachmentList().get(id);
      }
    } else {
      attachment = doc.getAttachment(filename);
    }

    if (attachment == null) {
      Object[] args = {filename};
      throw new XWikiException(
          XWikiException.MODULE_XWIKI_APP,
          XWikiException.ERROR_XWIKI_APP_ATTACHMENT_NOT_FOUND,
          "Attachment {0} not found",
          null,
          args);
    }

    XWikiPluginManager plugins = context.getWiki().getPluginManager();
    attachment = plugins.downloadAttachment(attachment, context);

    // Try to load the attachment content just to make sure that the attachment really exists
    // This will throw an exception if the attachment content isn't available
    try {
      attachment.getContentSize(context);
    } catch (XWikiException e) {
      Object[] args = {filename};
      throw new XWikiException(
          XWikiException.MODULE_XWIKI_APP,
          XWikiException.ERROR_XWIKI_APP_ATTACHMENT_NOT_FOUND,
          "Attachment content {0} not found",
          null,
          args);
    }

    long lastModifiedOnClient = request.getDateHeader("If-Modified-Since");
    long lastModifiedOnServer = attachment.getDate().getTime();
    if (lastModifiedOnClient != -1 && lastModifiedOnClient >= lastModifiedOnServer) {
      response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
      return null;
    }

    // Sending the content of the attachment
    if (request.getHeader(RANGE_HEADER_NAME) != null) {
      try {
        if (sendPartialContent(attachment, request, response, context)) {
          return null;
        }
      } catch (IOException ex) {
        // Broken response...
      }
    }
    sendContent(attachment, request, response, filename, context);
    return null;
  }