CCM NG/ccm-cms: JAX-RS based service for serving images.
git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4887 8810af33-2d31-482b-a856-94f89814c4dfccm-docs
parent
1775a3ed21
commit
486e0a3bc5
|
|
@ -89,6 +89,9 @@
|
||||||
<Logger name="org.librecms.contentsection.ContentSectionServlet"
|
<Logger name="org.librecms.contentsection.ContentSectionServlet"
|
||||||
level="debug">
|
level="debug">
|
||||||
</Logger>
|
</Logger>
|
||||||
|
<Logger name="org.librecms.contentsection.rs.Images"
|
||||||
|
level="debug">
|
||||||
|
</Logger>
|
||||||
<Logger name="com.arsdigita.web.DefaultApplicationFileResolver"
|
<Logger name="com.arsdigita.web.DefaultApplicationFileResolver"
|
||||||
level="debug">
|
level="debug">
|
||||||
</Logger>
|
</Logger>
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,32 @@
|
||||||
*/
|
*/
|
||||||
package org.librecms.contentsection.rs;
|
package org.librecms.contentsection.rs;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.librecms.assets.Image;
|
import org.librecms.assets.Image;
|
||||||
import org.librecms.contentsection.Asset;
|
import org.librecms.contentsection.Asset;
|
||||||
import org.librecms.contentsection.AssetRepository;
|
import org.librecms.contentsection.AssetRepository;
|
||||||
import org.librecms.contentsection.ContentSection;
|
import org.librecms.contentsection.ContentSection;
|
||||||
import org.librecms.contentsection.ContentSectionRepository;
|
import org.librecms.contentsection.ContentSectionRepository;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import javax.enterprise.context.RequestScoped;
|
import javax.enterprise.context.RequestScoped;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.DefaultValue;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,17 +54,55 @@ import javax.ws.rs.core.Response;
|
||||||
@Path("/{content-section}/images/")
|
@Path("/{content-section}/images/")
|
||||||
public class Images {
|
public class Images {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(Images.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ContentSectionRepository sectionRepo;
|
private ContentSectionRepository sectionRepo;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private AssetRepository assetRepo;
|
private AssetRepository assetRepo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the image requested by the provided content section and path.
|
||||||
|
*
|
||||||
|
* The URL for an image contains the content section and the path to the
|
||||||
|
* image. If there is no image for the provided content section and path an
|
||||||
|
* 404 error is returned.
|
||||||
|
*
|
||||||
|
* This method also accepts two parameters which can be specified as query
|
||||||
|
* parameters on the URL: {@code width} and {@code height}. If one or both
|
||||||
|
* are provided the image is scaled before it is send the the user agent
|
||||||
|
* which requested the image. The method preserves the aspect ratio of the
|
||||||
|
* image. If the parameters have different scale factors meaning that the
|
||||||
|
* aspect ratio of the image would not be preserved the parameter with the
|
||||||
|
* smallest difference to one if used. The other parameter is ignored and
|
||||||
|
* replaced with a value which preserves the aspect ratio.
|
||||||
|
*
|
||||||
|
* @param sectionName The name of the content section which contains the
|
||||||
|
* image.
|
||||||
|
* @param path The path to the image.
|
||||||
|
* @param widthParam The width to scale the image. If the value is 0 or
|
||||||
|
* less or the value is not a valid integer it parameter
|
||||||
|
* is ignored.
|
||||||
|
* @param heightParam The height to scale the image. If the value is 0 or
|
||||||
|
* less or the value is not a valid integer it parameter
|
||||||
|
* is ignored.
|
||||||
|
*
|
||||||
|
* @return A {@link Response} containing the scaled image or an error value.
|
||||||
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("/{path:.+}")
|
@Path("/{path:.+}")
|
||||||
public Response getImage(
|
public Response getImage(
|
||||||
@PathParam("content-section") final String sectionName,
|
@PathParam("content-section")
|
||||||
@PathParam("path") final String path) {
|
final String sectionName,
|
||||||
|
@PathParam("path")
|
||||||
|
final String path,
|
||||||
|
@QueryParam("width")
|
||||||
|
@DefaultValue("-1")
|
||||||
|
final String widthParam,
|
||||||
|
@QueryParam("height")
|
||||||
|
@DefaultValue("-1")
|
||||||
|
final String heightParam) {
|
||||||
|
|
||||||
final Optional<ContentSection> section = sectionRepo
|
final Optional<ContentSection> section = sectionRepo
|
||||||
.findByLabel(sectionName);
|
.findByLabel(sectionName);
|
||||||
|
|
@ -70,13 +121,77 @@ public class Images {
|
||||||
if (asset.get() instanceof Image) {
|
if (asset.get() instanceof Image) {
|
||||||
final Image image = (Image) asset.get();
|
final Image image = (Image) asset.get();
|
||||||
final byte[] data = image.getData();
|
final byte[] data = image.getData();
|
||||||
|
final String mimeType = image.getMimeType().toString();
|
||||||
|
|
||||||
|
final InputStream inputStream = new ByteArrayInputStream(data);
|
||||||
|
final BufferedImage bufferedImage;
|
||||||
|
final String imageFormat;
|
||||||
|
try {
|
||||||
|
final ImageInputStream imageInputStream = ImageIO
|
||||||
|
.createImageInputStream(inputStream);
|
||||||
|
final Iterator<ImageReader> readers = ImageIO
|
||||||
|
.getImageReaders(imageInputStream);
|
||||||
|
final ImageReader imageReader;
|
||||||
|
if (readers.hasNext()) {
|
||||||
|
imageReader = readers.next();
|
||||||
|
} else {
|
||||||
|
LOGGER.error("No image reader for image {} (UUID: {}) "
|
||||||
|
+ "available.",
|
||||||
|
image.getDisplayName(),
|
||||||
|
image.getUuid());
|
||||||
|
return Response.serverError().build();
|
||||||
|
}
|
||||||
|
imageReader.setInput(imageInputStream);
|
||||||
|
bufferedImage = imageReader.read(0);
|
||||||
|
imageFormat = imageReader.getFormatName();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOGGER.error("Failed to load image {} (UUID: {}).",
|
||||||
|
image.getDisplayName(),
|
||||||
|
image.getUuid());
|
||||||
|
LOGGER.error(ex);
|
||||||
|
return Response.serverError().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yes, this is correct. The parameters provided in the URL
|
||||||
|
// are expected to be integers. The private scaleImage method
|
||||||
|
// works with floats to be accurate (divisions are performed
|
||||||
|
// with the values for width and height)
|
||||||
|
final int width = parseScaleParameter(widthParam, "width");
|
||||||
|
final int height = parseScaleParameter(heightParam, "height");
|
||||||
|
final java.awt.Image scaledImage = scaleImage(bufferedImage,
|
||||||
|
width,
|
||||||
|
height);
|
||||||
|
|
||||||
|
final ByteArrayOutputStream outputStream
|
||||||
|
= new ByteArrayOutputStream();
|
||||||
|
final BufferedImage bufferedScaledImage = new BufferedImage(
|
||||||
|
scaledImage.getWidth(null),
|
||||||
|
scaledImage.getHeight(null),
|
||||||
|
bufferedImage.getType());
|
||||||
|
bufferedScaledImage
|
||||||
|
.getGraphics()
|
||||||
|
.drawImage(scaledImage, 0, 0, null);
|
||||||
|
try {
|
||||||
|
ImageIO
|
||||||
|
.write(bufferedScaledImage, imageFormat, outputStream);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOGGER.error("Failed to render scaled variant of image {} "
|
||||||
|
+ "(UUID: {}).",
|
||||||
|
image.getDisplayName(),
|
||||||
|
image.getUuid());
|
||||||
|
LOGGER.error(ex);
|
||||||
|
return Response.serverError().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// return Response
|
||||||
|
// .ok(String.format(
|
||||||
|
// "Requested image \"%s\" in content section \"%s\"",
|
||||||
|
// path,
|
||||||
|
// section.get().getLabel()),
|
||||||
|
// "text/plain")
|
||||||
|
// .build();
|
||||||
return Response
|
return Response
|
||||||
.ok(String.format(
|
.ok(outputStream.toByteArray(), mimeType)
|
||||||
"Requested image \"%s\" in content section \"%s\"",
|
|
||||||
path,
|
|
||||||
section.get().getLabel()),
|
|
||||||
"text/plain")
|
|
||||||
.build();
|
.build();
|
||||||
} else {
|
} else {
|
||||||
return Response
|
return Response
|
||||||
|
|
@ -107,4 +222,180 @@ public class Images {
|
||||||
// return builder.build();
|
// return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides several properties of an image to a user agent as JSON.
|
||||||
|
*
|
||||||
|
* @param sectionName The name of the content section which contains the
|
||||||
|
* image.
|
||||||
|
* @param path The path to the image.
|
||||||
|
*
|
||||||
|
* @return A {@link Response} with the informations about the requested
|
||||||
|
* image.
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/{path:.*}/properties")
|
||||||
|
public Response getImageProperties(
|
||||||
|
@PathParam("content-section") final String sectionName,
|
||||||
|
@PathParam("path") final String path) {
|
||||||
|
|
||||||
|
final Optional<ContentSection> section = sectionRepo
|
||||||
|
.findByLabel(sectionName);
|
||||||
|
if (!section.isPresent()) {
|
||||||
|
return Response
|
||||||
|
.status(Response.Status.NOT_FOUND)
|
||||||
|
.entity(String.format("No content section \"%s\" available.",
|
||||||
|
sectionName))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Optional<Asset> asset = assetRepo.findByPath(section.get(),
|
||||||
|
path);
|
||||||
|
|
||||||
|
if (asset.isPresent()) {
|
||||||
|
if (asset.get() instanceof Image) {
|
||||||
|
final Image image = (Image) asset.get();
|
||||||
|
final byte[] data = image.getData();
|
||||||
|
final String mimeType = image.getMimeType().toString();
|
||||||
|
|
||||||
|
final InputStream inputStream = new ByteArrayInputStream(data);
|
||||||
|
final BufferedImage bufferedImage;
|
||||||
|
try {
|
||||||
|
bufferedImage = ImageIO.read(inputStream);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOGGER.error("Failed to load image {} (UUID: {}).",
|
||||||
|
image.getDisplayName(),
|
||||||
|
image.getUuid());
|
||||||
|
LOGGER.error(ex);
|
||||||
|
return Response.serverError().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String imageProperties = String
|
||||||
|
.format("{%n"
|
||||||
|
+ " \"name\": \"%s\",%n"
|
||||||
|
+ " \"filename\": \"%s\",%n"
|
||||||
|
+ " \"mimetype\": \"%s\",%n"
|
||||||
|
+ " \"width\": %d,%n"
|
||||||
|
+ " \"height\": %d%n"
|
||||||
|
+ "}",
|
||||||
|
image.getDisplayName(),
|
||||||
|
image.getFileName(),
|
||||||
|
mimeType,
|
||||||
|
bufferedImage.getWidth(),
|
||||||
|
bufferedImage.getHeight());
|
||||||
|
|
||||||
|
return Response
|
||||||
|
.ok(imageProperties, "application/json")
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
return Response
|
||||||
|
.status(Response.Status.NOT_FOUND)
|
||||||
|
.entity(String
|
||||||
|
.format("The asset found at the requested path \"%s\" "
|
||||||
|
+ "is not an image.",
|
||||||
|
path))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Response
|
||||||
|
.status(Response.Status.NOT_FOUND)
|
||||||
|
.entity(String
|
||||||
|
.format("The requested image \"%s\" does not exist.",
|
||||||
|
path))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for parsing the parameters for scaling an image into
|
||||||
|
* integers.
|
||||||
|
*
|
||||||
|
* @param parameterValue The value to parse as integer.
|
||||||
|
* @param parameter The name of the parameter (used for logging
|
||||||
|
* output).
|
||||||
|
*
|
||||||
|
* @return The integer value of the parameter or -1 if the provided value is
|
||||||
|
* not a valid integer.
|
||||||
|
*/
|
||||||
|
private int parseScaleParameter(final String parameterValue,
|
||||||
|
final String parameter) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(parameterValue);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
LOGGER.warn("Provided value \"{}\" for parameter \"{}\" is "
|
||||||
|
+ "not an integer. Ignoring value.",
|
||||||
|
parameterValue,
|
||||||
|
parameter);
|
||||||
|
LOGGER.warn(ex);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for scaling the image while preserving the aspect ratio of
|
||||||
|
* the image.
|
||||||
|
*
|
||||||
|
* @param image The image to scale.
|
||||||
|
* @param scaleToWidth The width to which is scaled.
|
||||||
|
* @param scaleToHeight The height the which image is scaled.
|
||||||
|
*
|
||||||
|
* @return The scaled image.
|
||||||
|
*/
|
||||||
|
private java.awt.Image scaleImage(final BufferedImage image,
|
||||||
|
final float scaleToWidth,
|
||||||
|
final float scaleToHeight) {
|
||||||
|
|
||||||
|
final float originalWidth = image.getWidth();
|
||||||
|
final float originalHeight = image.getHeight();
|
||||||
|
final float originalAspectRatio = originalWidth / originalHeight;
|
||||||
|
|
||||||
|
if (scaleToWidth > 0 && scaleToHeight > 0) {
|
||||||
|
//Check if parameters preserve aspectRatio. If not use the smaller
|
||||||
|
//scale factor.
|
||||||
|
|
||||||
|
final float scaleToAspectRatio = scaleToWidth / scaleToHeight;
|
||||||
|
if (Math.abs(scaleToAspectRatio - originalAspectRatio) < 0.009f) {
|
||||||
|
// Scale the image.
|
||||||
|
|
||||||
|
return image.getScaledInstance(Math.round(scaleToWidth),
|
||||||
|
Math.round(scaleToHeight),
|
||||||
|
java.awt.Image.SCALE_SMOOTH);
|
||||||
|
} else {
|
||||||
|
//Use the scale factor nearer to one for both dimensions
|
||||||
|
final float scaleFactorWidth = scaleToWidth / originalWidth;
|
||||||
|
final float scaleFactorHeight = scaleToHeight / originalHeight;
|
||||||
|
final float differenceWidth = Math.abs(scaleFactorWidth - 1);
|
||||||
|
final float differenceHeight = Math.abs(scaleFactorHeight - 1);
|
||||||
|
|
||||||
|
final float scaleFactor;
|
||||||
|
if (differenceWidth < differenceHeight) {
|
||||||
|
scaleFactor = scaleFactorWidth;
|
||||||
|
} else {
|
||||||
|
scaleFactor = scaleFactorHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return scaleImage(image,
|
||||||
|
originalWidth * scaleFactor,
|
||||||
|
originalHeight * scaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (scaleToWidth > 0 && scaleToHeight <= 0) {
|
||||||
|
//Calculate the height to which to image is scaled based on the
|
||||||
|
//scale factor for the width
|
||||||
|
final float scaleFactor = scaleToWidth / originalWidth;
|
||||||
|
final float height = originalHeight * scaleFactor;
|
||||||
|
|
||||||
|
return scaleImage(image, scaleToWidth, height);
|
||||||
|
} else if (scaleToWidth <= 0 && scaleToHeight >= 0) {
|
||||||
|
//Calculate the width to which to image is scaled based on the
|
||||||
|
//scale factor for the height
|
||||||
|
final float scaleFactor = scaleToHeight / originalHeight;
|
||||||
|
final float width = originalWidth * scaleFactor;
|
||||||
|
|
||||||
|
return scaleImage(image, width, scaleToHeight);
|
||||||
|
} else {
|
||||||
|
//Return the image as is.
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue