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,8 +89,11 @@
|
|||
<Logger name="org.librecms.contentsection.ContentSectionServlet"
|
||||
level="debug">
|
||||
</Logger>
|
||||
<Logger name="org.librecms.contentsection.rs.Images"
|
||||
level="debug">
|
||||
</Logger>
|
||||
<Logger name="com.arsdigita.web.DefaultApplicationFileResolver"
|
||||
level="debug">
|
||||
level="debug">
|
||||
</Logger>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
|
|
@ -18,19 +18,32 @@
|
|||
*/
|
||||
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.contentsection.Asset;
|
||||
import org.librecms.contentsection.AssetRepository;
|
||||
import org.librecms.contentsection.ContentSection;
|
||||
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 javax.enterprise.context.RequestScoped;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
|
|
@ -41,17 +54,55 @@ import javax.ws.rs.core.Response;
|
|||
@Path("/{content-section}/images/")
|
||||
public class Images {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(Images.class);
|
||||
|
||||
@Inject
|
||||
private ContentSectionRepository sectionRepo;
|
||||
|
||||
@Inject
|
||||
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
|
||||
@Path("/{path:.+}")
|
||||
public Response getImage(
|
||||
@PathParam("content-section") final String sectionName,
|
||||
@PathParam("path") final String path) {
|
||||
@PathParam("content-section")
|
||||
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
|
||||
.findByLabel(sectionName);
|
||||
|
|
@ -70,13 +121,77 @@ public class Images {
|
|||
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;
|
||||
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
|
||||
.ok(String.format(
|
||||
"Requested image \"%s\" in content section \"%s\"",
|
||||
path,
|
||||
section.get().getLabel()),
|
||||
"text/plain")
|
||||
.ok(outputStream.toByteArray(), mimeType)
|
||||
.build();
|
||||
} else {
|
||||
return Response
|
||||
|
|
@ -107,4 +222,180 @@ public class Images {
|
|||
// 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