/**
  * Store the requested attachment on the filesystem and return a {@code file://} URL where FOP can
  * access that file.
  *
  * @param wiki the name of the owner document's wiki
  * @param space the name of the owner document's space
  * @param name the name of the owner document
  * @param filename the name of the attachment
  * @param revision an optional attachment version
  * @param context the current request context
  * @return a {@code file://} URL where the attachment has been stored
  * @throws Exception if the attachment can't be retrieved from the database and stored on the
  *     filesystem
  */
 private URL getURL(
     String wiki,
     String space,
     String name,
     String filename,
     String revision,
     XWikiContext context)
     throws Exception {
   Map<String, File> usedFiles = getFileMapping(context);
   String key = getAttachmentKey(space, name, filename, revision);
   if (!usedFiles.containsKey(key)) {
     File file = getTemporaryFile(key, context);
     XWikiDocument doc =
         context
             .getWiki()
             .getDocument(
                 new DocumentReference(
                     StringUtils.defaultString(wiki, context.getDatabase()), space, name),
                 context);
     XWikiAttachment attachment = doc.getAttachment(filename);
     if (StringUtils.isNotEmpty(revision)) {
       attachment = attachment.getAttachmentRevision(revision, context);
     }
     FileOutputStream fos = new FileOutputStream(file);
     IOUtils.copy(attachment.getContentInputStream(context), fos);
     fos.close();
     usedFiles.put(key, file);
   }
   return usedFiles.get(key).toURI().toURL();
 }
 /**
  * Send the attachment content in the response.
  *
  * @param attachment the attachment to get content from
  * @param request the current client request
  * @param response the response to write to.
  * @param filename the filename to show in the message in case an exception needs to be thrown
  * @param context the XWikiContext just in case it is needed to load the attachment content
  * @throws XWikiException if something goes wrong
  */
 private static void sendContent(
     final XWikiAttachment attachment,
     final XWikiRequest request,
     final XWikiResponse response,
     final String filename,
     final XWikiContext context)
     throws XWikiException {
   InputStream stream = null;
   try {
     setCommonHeaders(attachment, request, response, context);
     response.setContentLength(attachment.getContentSize(context));
     stream = attachment.getContentInputStream(context);
     IOUtils.copy(stream, response.getOutputStream());
   } catch (IOException e) {
     throw new XWikiException(
         XWikiException.MODULE_XWIKI_APP,
         XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION,
         "Exception while sending response",
         e);
   } finally {
     if (stream != null) {
       IOUtils.closeQuietly(stream);
     }
   }
 }
 /**
  * Write a byte range from the attachment to the response, if the requested range is valid and
  * falls within the file limits.
  *
  * @param attachment the attachment to get content from
  * @param start the first byte to write
  * @param end the last byte to write
  * @param request the current client request
  * @param response the response to write to.
  * @param context the current request context
  * @throws XWikiException if the attachment content cannot be retrieved
  * @throws IOException if the response cannot be written
  */
 private static void writeByteRange(
     final XWikiAttachment attachment,
     Long start,
     Long end,
     final XWikiRequest request,
     final XWikiResponse response,
     final XWikiContext context)
     throws XWikiException, IOException {
   if (start >= 0 && start < attachment.getContentSize(context)) {
     InputStream data = attachment.getContentInputStream(context);
     data = new BoundedInputStream(data, end + 1);
     data.skip(start);
     setCommonHeaders(attachment, request, response, context);
     response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
     if ((end - start + 1L) < Integer.MAX_VALUE) {
       response.setContentLength((int) (end - start + 1));
     }
     response.setHeader(
         "Content-Range",
         "bytes " + start + "-" + end + SEPARATOR + attachment.getContentSize(context));
     IOUtils.copyLarge(data, response.getOutputStream());
   } else {
     response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
   }
 }
 public void addToCache(String key, XWikiAttachment attachment) throws XWikiException {
   if (getImageCache() != null) {
     try {
       getImageCache()
           .set(key, IOUtils.toByteArray(attachment.getContentInputStream(getContext())));
     } catch (IOException exp) {
       LOGGER.error("Failed to cache image [" + key + "].", exp);
     }
   } else {
     LOGGER.info("Caching of images deactivated.");
   }
 }
  /**
   * Reduces the size (i.e. the number of bytes) of an image by scaling its width and height and by
   * reducing its compression quality. This helps decreasing the time needed to download the image
   * attachment.
   *
   * @param attachment the image to be shrunk
   * @param requestedWidth the desired image width; this value is taken into account only if it is
   *     greater than zero and less than the current image width
   * @param requestedHeight the desired image height; this value is taken into account only if it is
   *     greater than zero and less than the current image height
   * @param keepAspectRatio {@code true} to preserve the image aspect ratio even when both requested
   *     dimensions are properly specified (in this case the image will be resized to best fit the
   *     rectangle with the requested width and height), {@code false} otherwise
   * @param requestedQuality the desired compression quality
   * @param context the XWiki context
   * @return the modified image attachment
   * @throws Exception if shrinking the image fails
   */
  private XWikiAttachment shrinkImage(
      XWikiAttachment attachment,
      int requestedWidth,
      int requestedHeight,
      boolean keepAspectRatio,
      float requestedQuality,
      XWikiContext context)
      throws Exception {
    Image image = this.imageProcessor.readImage(attachment.getContentInputStream(context));

    // Compute the new image dimension.
    int currentWidth = image.getWidth(null);
    int currentHeight = image.getHeight(null);
    int[] dimensions =
        reduceImageDimensions(
            currentWidth, currentHeight, requestedWidth, requestedHeight, keepAspectRatio);

    float quality = requestedQuality;
    if (quality < 0) {
      // If no scaling is needed and the quality parameter is not specified, return the original
      // image.
      if (dimensions[0] == currentWidth && dimensions[1] == currentHeight) {
        return attachment;
      }
      quality = this.defaultQuality;
    }

    // Scale the image to the new dimensions.
    RenderedImage shrunkImage = this.imageProcessor.scaleImage(image, dimensions[0], dimensions[1]);

    // Create an image attachment for the shrunk image.
    XWikiAttachment thumbnail = (XWikiAttachment) attachment.clone();
    thumbnail.loadContent(context);

    OutputStream acos = thumbnail.getAttachment_content().getContentOutputStream();
    this.imageProcessor.writeImage(shrunkImage, attachment.getMimeType(context), quality, acos);

    IOUtils.closeQuietly(acos);

    return thumbnail;
  }
 /**
  * @param attachment an image attachment
  * @param context the XWiki context
  * @return the height of the specified image
  * @throws IOException if reading the image from the attachment content fails
  * @throws XWikiException if reading the attachment content fails
  */
 public int getHeight(XWikiAttachment attachment, XWikiContext context)
     throws IOException, XWikiException {
   return this.imageProcessor.readImage(attachment.getContentInputStream(context)).getHeight(null);
 }