Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2530] AdaptiveImage: control skipping/requiring transformation via OSGi config #2534

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.collections4.ListUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.CharEncoding;
Expand Down Expand Up @@ -105,8 +106,11 @@ public class AdaptiveImageServlet extends SlingSafeMethodsServlet {
private static final String DEFAULT_MIME = "image/jpeg";
private static final String SELECTOR_QUALITY_KEY = "quality";
private static final String SELECTOR_WIDTH_KEY = "width";
private static final List<String> NON_TRANSFORMABLE_IMAGE_TYPES = List.of("gif", "svg");
private int defaultResizeWidth;
private int maxInputWidth;
private List<String> nonTransformableImageTypes;
private List<String> forcedTransformationImageTypes;

private AdaptiveImageServletMetrics metrics;

Expand All @@ -115,12 +119,15 @@ public class AdaptiveImageServlet extends SlingSafeMethodsServlet {
private transient AssetStore assetStore;

public AdaptiveImageServlet(MimeTypeService mimeTypeService, AssetStore assetStore, AdaptiveImageServletMetrics metrics,
int defaultResizeWidth, int maxInputWidth) {
int defaultResizeWidth, int maxInputWidth, List<String> nonTransformableImageTypes,
List<String> forcedTransformationImageTypes) {
this.mimeTypeService = mimeTypeService;
this.assetStore = assetStore;
this.metrics = metrics;
this.defaultResizeWidth = defaultResizeWidth > 0 ? defaultResizeWidth : DEFAULT_RESIZE_WIDTH;
this.maxInputWidth = maxInputWidth > 0 ? maxInputWidth : DEFAULT_MAX_SIZE;
this.nonTransformableImageTypes = ListUtils.union(nonTransformableImageTypes, NON_TRANSFORMABLE_IMAGE_TYPES);
this.forcedTransformationImageTypes = forcedTransformationImageTypes;
}

@Override
Expand All @@ -132,7 +139,6 @@ protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHtt
List<String> selectorList = selectorToList(requestPathInfo.getSelectorString());
String suffix = requestPathInfo.getSuffix();
String imageName = StringUtils.isNotEmpty(suffix) ? FilenameUtils.getName(suffix) : "";

if (StringUtils.isNotEmpty(suffix)) {
String suffixExtension = FilenameUtils.getExtension(suffix);
if (StringUtils.isNotEmpty(suffixExtension)) {
Expand Down Expand Up @@ -221,6 +227,7 @@ protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHtt
return;
}
}

if (!handleIfModifiedSinceHeader(request, response, lastModifiedEpoch)) {

Map<String, Integer> transformationMap = getTransformationMap(selectorList, component, request);
Expand Down Expand Up @@ -251,8 +258,8 @@ protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHtt
protected void transformAndStreamAsset(SlingHttpServletResponse response, ValueMap componentProperties, int resizeWidth, double quality,
Asset asset, String imageType, String imageName) throws IOException {
String extension = mimeTypeService.getExtension(imageType);
if ("gif".equalsIgnoreCase(extension) || "svg".equalsIgnoreCase(extension)) {
LOGGER.debug("GIF or SVG asset detected; will render the original rendition.");
if (nonTransformableImageTypes.contains(extension)) {
LOGGER.debug("{} asset detected; will render the original rendition.", extension);
metrics.markOriginalRenditionUsed();
try (InputStream is = asset.getOriginal().getStream()) {
if (is != null) {
Expand All @@ -265,7 +272,10 @@ protected void transformAndStreamAsset(SlingHttpServletResponse response, ValueM
Rectangle rectangle = getCropRect(componentProperties);
boolean flipHorizontally = componentProperties.get(Image.PN_FLIP_HORIZONTAL, Boolean.FALSE);
boolean flipVertically = componentProperties.get(Image.PN_FLIP_VERTICAL, Boolean.FALSE);
if (rotationAngle != 0 || rectangle != null || resizeWidth > 0 || flipHorizontally || flipVertically) {
if (rotationAngle != 0 || rectangle != null || resizeWidth > 0 || flipHorizontally || flipVertically ||
forcedTransformationImageTypes.contains(extension)) {
LOGGER.debug("Applying transformation; rotation: {}, cropping: {}, resize: {} flip hor: {}, flip vert: {}, extension: {}",
rotationAngle, rectangle, resizeWidth, flipHorizontally, flipVertically, extension);
int originalWidth = getDimension(asset.getMetadataValue(DamConstants.TIFF_IMAGEWIDTH));
int originalHeight = getDimension(asset.getMetadataValue(DamConstants.TIFF_IMAGELENGTH));
Layer layer = null;
Expand Down Expand Up @@ -383,10 +393,10 @@ protected void transformAndStreamAsset(SlingHttpServletResponse response, ValueM
private void transformAndStreamFile(SlingHttpServletResponse response, ValueMap componentProperties, int
resizeWidth, double quality, Resource imageFile, String imageType, String imageName) throws
IOException {
final String extension = mimeTypeService.getExtension(imageType);
try (InputStream is = imageFile.adaptTo(InputStream.class)) {
if ("gif".equalsIgnoreCase(mimeTypeService.getExtension(imageType))
|| "svg".equalsIgnoreCase(mimeTypeService.getExtension(imageType))) {
LOGGER.debug("GIF or SVG file detected; will render the original file.");
if (nonTransformableImageTypes.contains(extension)) {
LOGGER.debug("{} file detected; will render the original file.", extension);
if (is != null) {
stream(response, is, imageType, imageName);
}
Expand All @@ -397,7 +407,8 @@ private void transformAndStreamFile(SlingHttpServletResponse response, ValueMap
boolean flipHorizontally = componentProperties.get(Image.PN_FLIP_HORIZONTAL, Boolean.FALSE);
boolean flipVertically = componentProperties.get(Image.PN_FLIP_VERTICAL, Boolean.FALSE);
if (is != null) {
if (rotationAngle != 0 || rectangle != null || resizeWidth > 0 || flipHorizontally || flipVertically) {
if (rotationAngle != 0 || rectangle != null || resizeWidth > 0 || flipHorizontally || flipVertically ||
forcedTransformationImageTypes.contains(extension)) {
Layer layer = new Layer(is);
if (rectangle != null) {
layer.crop(rectangle);
Expand Down Expand Up @@ -610,7 +621,8 @@ private EnhancedRendition filter(@NotNull EnhancedRendition rendition) throws IO
private void streamOrConvert(@NotNull SlingHttpServletResponse response, @NotNull EnhancedRendition rendition, @NotNull String imageType,
String imageName, int resizeWidth, double quality) throws IOException {
Dimension dimension = rendition.getDimension();
if (rendition.getMimeType().equals(imageType)) {
final String extension = mimeTypeService.getExtension(imageType);
if (rendition.getMimeType().equals(imageType) && !forcedTransformationImageTypes.contains(extension)) {
LOGGER.debug("Found rendition {}/{} has a width of {}px and does not require a resize for requested width of {}px",
rendition.getAsset().getPath(), rendition.getName(), dimension != null ? dimension.getWidth() : null, resizeWidth);
try (InputStream is = rendition.getStream()) {
Expand All @@ -631,7 +643,7 @@ private void streamOrConvert(@NotNull SlingHttpServletResponse response, @NotNul
}
} else {
LOGGER.debug("Found rendition {}/{} has a width of {}px and does not require a resize for requested width of {}px " +
"but the rendition is not of the requested type {}, need to convert",
"but the rendition is not of the requested type {} or forces conversion, need to convert",
rendition.getAsset().getPath(), rendition.getName(), dimension != null ? dimension.getWidth() : null, resizeWidth, imageType);
resizeAndStreamLayer(response, layer, imageType, 0, quality);
}
Expand Down Expand Up @@ -993,8 +1005,6 @@ private int getResizeWidth(@NotNull Resource imageResource, @NotNull SlingHttpSe
return allowedResizeWidth;
}



private enum Source {
ASSET,
FILE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public class AdaptiveImageServletMappingConfigurationConsumer {

@Reference
private ConfigurationAdmin configurationAdmin;

@Reference
AdaptiveImageServletMetrics metrics;

Expand Down Expand Up @@ -171,7 +171,9 @@ private void updateServletRegistrations() {
assetStore,
metrics,
oldAISDefaultResizeWidth > 0 ? oldAISDefaultResizeWidth : config.getDefaultResizeWidth(),
config.getMaxSize()),
config.getMaxSize(),
config.getNonTransformableImageTypes(),
config.getForcedTransformationImageTypes()),
properties
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ public class AdaptiveImageServletMappingConfigurationFactory {
)
int maxSize() default AdaptiveImageServlet.DEFAULT_MAX_SIZE;

@AttributeDefinition(
name = "Non-transformable image types",
description = "Images of the following types (identified by lower-case extension) will never be transformed (e.g. " +
"rotated, cropped, etc.). svg and gif images are never transformed, even if not defined in this list."
)
String[] nonTransformableImageTypes() default { "gif", "svg" };

@AttributeDefinition(
name ="Forced transformation image types",
description = "Images of the following types (identified by lower-case extensions) will always be transformed, reg. of " +
"editorial application of rotation, transformation, etc. - ensuring e.g. compression is applied."
)
String[] forcedTransformationImageTypes() default{ } ;
}

private List<String> resourceTypes;
Expand All @@ -93,6 +106,10 @@ public class AdaptiveImageServletMappingConfigurationFactory {

private int maxSize;

private List<String> nonTransformableImageTypes;

private List<String> forcedTransformationImageTypes;

/**
* Invoked when a configuration is created or modified.
*
Expand All @@ -106,6 +123,8 @@ void configure(Config config) {
extensions = getValues(config.extensions());
defaultResizeWidth = config.defaultResizeWidth();
maxSize = config.maxSize();
nonTransformableImageTypes = getValues(config.nonTransformableImageTypes());
forcedTransformationImageTypes = getValues(config.forcedTransformationImageTypes());
}

/**
Expand Down Expand Up @@ -155,6 +174,21 @@ public int getMaxSize() {
return maxSize;
}

/**
* Returns the image types' extensions that will not be transformed by the {@link AdaptiveImageServlet}.
* @return
*/
@NotNull
public List<String> getNonTransformableImageTypes() { return Collections.unmodifiableList(this.nonTransformableImageTypes); }

/**
* Returns the image types' extensions that will always be transformed by the {@link AdaptiveImageServlet} e.g. enforcing compression
* independently from editorial applied changes.
* @return
*/
@NotNull
public List<String> getForcedTransformationImageTypes() { return Collections.unmodifiableList(this.forcedTransformationImageTypes); }

/**
* Internal helper for filtering out null and empty values from the configuration options.
*
Expand All @@ -176,6 +210,7 @@ private List<String> getValues(@NotNull String[] config) {
@Override
public String toString() {
return "{resourceTypes: " + resourceTypes.toString() + ", selectors: " + selectors.toString() + ", extensions: " + extensions
.toString() + ", defaultResizeWidth: " + defaultResizeWidth + "}";
.toString() + ", defaultResizeWidth: " + defaultResizeWidth + ", nonTransformableImageTypes: " + nonTransformableImageTypes
.toString() + ", forcedTransformationImageTypes: " + forcedTransformationImageTypes.toString() + "}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public class AbstractImageTest {
protected static final String IMAGE28_PATH = PAGE + "/jcr:content/root/image28";
protected static final String IMAGE29_PATH = PAGE + "/jcr:content/root/image29";
protected static final String IMAGE30_PATH = PAGE + "/jcr:content/root/image30";
protected static final String IMAGE31_PATH = PAGE + "/jcr:content/root/image31";
protected static final String IMAGE45_PATH = PAGE + "/jcr:content/root/image45";
protected static final String IMAGE59_PATH = PAGE + "/jcr:content/root/image59";
protected static final String LIST01_PATH = PAGE + "/jcr:content/root/list01";
Expand All @@ -89,6 +90,7 @@ public class AbstractImageTest {
protected static final String PNG_IMAGE_BINARY_NAME = "Adobe_Systems_logo_and_wordmark.png";
protected static final String GIF_IMAGE_BINARY_NAME = "Adobe_Systems_logo_and_wordmark.gif";
protected static final String JPG_IMAGE_BINARY_NAME = "Adobe_Systems_logo_and_wordmark.jpg";
protected static final String JPG_FULL_QUALITY_IMAGE_BINARY_NAME = "Adobe_Systems_logo_and_wordmark_full_quality.jpg";
protected static final String _1PX_IMAGE_BINARY_NAME = "1x1.png";
protected static final String _40MPX_IMAGE_BINARY_NAME = "20000x20000.png";
protected static final String TRANSPARENT_IMAGE_BINARY_NAME = "transparent_hd.png";
Expand All @@ -99,6 +101,7 @@ public class AbstractImageTest {
protected static final String GIF_ASSET_PATH = "/content/dam/core/images/" + GIF_IMAGE_BINARY_NAME;
protected static final String TIFF_ASSET_PATH = "/content/dam/core/images/" + TIFF_IMAGE_BINARY_NAME;
protected static final String SVG_ASSET_PATH = "/content/dam/core/images/" + SVG_IMAGE_BINARY_NAME;
protected static final String JPG_FULL_QUALITY_ASSET_PATH = "/content/dam/core/images/" + JPG_FULL_QUALITY_IMAGE_BINARY_NAME;
protected static final String GIF5_FILE_PATH = IMAGE5_PATH + "/file";
protected static final String PNG3_FILE_PATH = IMAGE3_PATH + "/file";
protected static final String PNG10_FILE_PATH = IMAGE10_PATH + "/file";
Expand Down Expand Up @@ -147,6 +150,8 @@ protected void internalSetUp(String testBase) {
context.load().binaryFile("/image/" + "cq5dam.web.1280.1280_" + GIF_IMAGE_BINARY_NAME, GIF_ASSET_PATH +
"/jcr:content/renditions/cq5dam.web.1280.1280.gif");
context.load().binaryFile("/image/" + SVG_IMAGE_BINARY_NAME, SVG_ASSET_PATH + "/jcr:content/renditions/original");
context.load().binaryFile("/image/" + JPG_FULL_QUALITY_IMAGE_BINARY_NAME, JPG_FULL_QUALITY_ASSET_PATH +
"/jcr:content/renditions/original");
context.load().binaryFile("/image/" + GIF_IMAGE_BINARY_NAME, GIF5_FILE_PATH, StandardImageHandler.GIF_MIMETYPE);
context.load().binaryFile("/image/" + PNG_IMAGE_BINARY_NAME, PNG3_FILE_PATH, StandardImageHandler.PNG1_MIMETYPE);
context.load().binaryFile("/image/" + PNG_IMAGE_BINARY_NAME, PNG10_FILE_PATH, StandardImageHandler.PNG1_MIMETYPE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,20 @@ public int defaultResizeWidth() {
public int maxSize() {
return AdaptiveImageServlet.DEFAULT_MAX_SIZE;
}

@Override
public String[] nonTransformableImageTypes() { return new String[] {"gif", "svg"}; }

@Override
public String[] forcedTransformationImageTypes() { return new String[] { }; }
});
testValues(new String[] {"core/image"}, configurationFactory.getResourceTypes());
testValues(new String[] {"coreimg"}, configurationFactory.getSelectors());
testValues(new String[] {"jpg", "gif", "png"}, configurationFactory.getExtensions());
assertEquals("{resourceTypes: [core/image], selectors: [coreimg], extensions: [jpg, gif, png], defaultResizeWidth: 1280}",
testValues(new String[] {"gif", "svg" }, configurationFactory.getNonTransformableImageTypes());
testValues(new String[] { }, configurationFactory.getForcedTransformationImageTypes());
assertEquals("{resourceTypes: [core/image], selectors: [coreimg], extensions: [jpg, gif, png], defaultResizeWidth: 1280, " +
"nonTransformableImageTypes: [gif, svg], forcedTransformationImageTypes: []}",
configurationFactory.toString());
}

Expand Down