RenditionMetadata.java
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2014 wcm.io
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package io.wcm.handler.mediasource.dam.impl;
import java.io.InputStream;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.jackrabbit.oak.commons.LazyValue;
import org.apache.sling.api.adapter.SlingAdaptable;
import org.apache.sling.api.resource.Resource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.day.cq.dam.api.Rendition;
import com.day.image.Layer;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.wcm.handler.media.Dimension;
import io.wcm.handler.media.MediaFileType;
import io.wcm.handler.media.UriTemplate;
import io.wcm.handler.media.UriTemplateType;
import io.wcm.handler.media.format.MediaFormat;
import io.wcm.handler.media.format.Ratio;
import io.wcm.handler.media.impl.ImageFileServlet;
import io.wcm.handler.media.impl.MediaFileServletConstants;
import io.wcm.handler.mediasource.dam.AssetRendition;
import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaPath;
import io.wcm.handler.mediasource.dam.impl.weboptimized.WebOptimizedImageDeliveryParams;
import io.wcm.wcm.commons.contenttype.FileExtension;
/**
* Wrapper class for rendition metadata retrieved from DAM rendition filenames.
*/
class RenditionMetadata extends SlingAdaptable implements Comparable<RenditionMetadata> {
private final Rendition rendition;
private final String fileName;
private final String fileExtension;
private LazyValue<Dimension> dimensionLazyValue;
private final boolean isImage;
private final boolean isVectorImage;
private MediaFormat mediaFormat;
/**
* @param rendition DAM rendition
*/
RenditionMetadata(Rendition rendition) {
this.rendition = rendition;
// get filename and extension
this.fileName = AssetRendition.getFilename(rendition);
this.fileExtension = FilenameUtils.getExtension(this.fileName);
this.isImage = MediaFileType.isImage(this.fileExtension);
this.isVectorImage = MediaFileType.isVectorImage(this.fileExtension);
// read dimensions on demand, as it can be expensive.
// if dimension cannot be obtained use a dimension with width/height=0
this.dimensionLazyValue = new LazyValue<>() {
@Override
protected Dimension createValue() {
Dimension result = AssetRendition.getDimension(rendition);
if (result == null) {
result = new Dimension(0, 0);
}
return result;
}
};
}
/**
* @return True if rendition is an image
*/
public boolean isImage() {
return this.isImage;
}
/**
* @return True if rendition is a vector image
*/
public boolean isVectorImage() {
return this.isVectorImage;
}
/**
* @return DAM rendition
*/
public Rendition getRendition() {
return this.rendition;
}
/**
* @return File name
*/
public String getFileName(boolean contentDispositionAttachment) {
if (MediaFileType.isBrowserImage(getFileExtension())
|| !MediaFileType.isImage(getFileExtension())
|| contentDispositionAttachment
|| isVectorImage()) {
return this.fileName;
}
else {
return ImageFileServlet.getImageFileName(this.fileName);
}
}
/**
* @return File size
*/
public long getFileSize() {
return this.rendition.getSize();
}
/**
* @return File extension
*/
public String getFileExtension() {
return this.fileExtension;
}
/**
* @return Mime type
*/
public String getMimeType() {
return this.rendition.getMimeType();
}
/**
* @return Image width
*/
public long getWidth() {
return dimensionLazyValue.get().getWidth();
}
/**
* @return Image height
*/
public long getHeight() {
return dimensionLazyValue.get().getHeight();
}
/**
* @return Media format that matches with the resolved rendition. Null if no media format was specified for resolving.
*/
public MediaFormat getMediaFormat() {
return this.mediaFormat;
}
/**
* @param mediaFormat Media format that matches with the resolved rendition. Null if no media format was specified for
* resolving.
*/
public void setMediaFormat(MediaFormat mediaFormat) {
this.mediaFormat = mediaFormat;
}
/**
* @param contentDispositionAttachment Force content disposition download header.
* @return Media path (not externalized)
*/
public @NotNull String getMediaPath(boolean contentDispositionAttachment) {
if (contentDispositionAttachment) {
return buildMediaPath(getRendition().getPath() + "." + MediaFileServletConstants.SELECTOR
+ "." + MediaFileServletConstants.SELECTOR_DOWNLOAD
+ "." + MediaFileServletConstants.EXTENSION, getFileName(contentDispositionAttachment));
}
else if (MediaFileType.isVectorImage(getFileExtension())) {
return buildMediaPath(getRendition().getPath() + "." + MediaFileServletConstants.SELECTOR
+ "." + MediaFileServletConstants.EXTENSION, getFileName(contentDispositionAttachment));
}
else if (MediaFileType.isBrowserImage(getFileExtension()) || !MediaFileType.isImage(getFileExtension())) {
// use "deep URL" to reference rendition directly
// do not use Asset URL for original rendition because it creates conflicts for dispatcher cache (filename vs. directory for asset resource name)
return buildMediaPath(this.rendition.getPath() + ".", getFileName(contentDispositionAttachment));
}
else {
// image rendition uses a file extension that cannot be displayed in browser directly - render via ImageFileServlet
return buildMediaPath(getRendition().getPath() + "." + ImageFileServlet.SELECTOR
+ "." + getWidth() + "." + getHeight()
+ "." + MediaFileServletConstants.EXTENSION, getFileName(contentDispositionAttachment));
}
}
/**
* Returns a dynamic media path part (object and further URL modifiers, the string after "/is/image/").
* @param contentDispositionAttachment Force content disposition download header.
* @param damContext DAM context
* @return Dynamic media path part or null if dynamic media not supported for this rendition
*/
public @Nullable String getDynamicMediaPath(boolean contentDispositionAttachment, DamContext damContext) {
if (contentDispositionAttachment) {
// serve static content from dynamic media for content disposition attachment
return DynamicMediaPath.buildContent(damContext, true);
}
else if ((MediaFileType.isBrowserImage(getFileExtension())
&& (MediaFileType.isVectorImage(getFileExtension()) || StringUtils.equals(getFileExtension(), FileExtension.GIF)))
|| !MediaFileType.isImage(getFileExtension())) {
// serve non-image requests or Vector/GIF images as static content from dynamic media
// (vector can be scaled in browser directly, GIF may be animated which is not supported by dynamic media)
return DynamicMediaPath.buildContent(damContext, false);
}
else {
// display original rendition with original file size - or image rendition that uses a file extension
// that cannot be displayed in browser directly - render via dynamic media
return DynamicMediaPath.buildImage(damContext, getWidth(), getHeight());
}
}
/**
* Returns a web-optimized image delivery URL.
* @param damContext DAM context
* @return URL or null if web-optimized image delivery is not supported
*/
public @Nullable String getWebOptimizedImageDeliveryPath(DamContext damContext) {
return damContext.getWebOptimizedImageDeliveryUrl(new WebOptimizedImageDeliveryParams());
}
/**
* Checks if this rendition matches the given width/height.
* @param checkWidth Width
* @param checkHeight Height
* @return true if matches
*/
@SuppressWarnings("java:S1126")
public boolean matches(long checkWidth, long checkHeight) {
if (checkWidth != 0 && checkWidth != getWidth()) {
return false;
}
if (checkHeight != 0 && checkHeight != getHeight()) {
return false;
}
return true;
}
/**
* Checks if this rendition matches the given width/height/ration restrictions.
* @param minWidth Min. width
* @param minHeight Min. height
* @param maxWidth Max. width
* @param maxHeight Max. height
* @param minWidthHeight Min. width/height (the longest edge)
* @param ratio Ratio
* @return true if matches
*/
public boolean matches(long minWidth, long minHeight, long maxWidth, long maxHeight, long minWidthHeight, double ratio) {
if (minWidthHeight > 0 && (getWidth() < minWidthHeight && getHeight() < minWidthHeight)) {
return false;
}
if (minWidth > 0 && getWidth() < minWidth) {
return false;
}
if (minHeight > 0 && getHeight() < minHeight) {
return false;
}
if (maxWidth > 0 && getWidth() > maxWidth) {
return false;
}
if (maxHeight > 0 && getHeight() > maxHeight) {
return false;
}
if (ratio > 0) {
double renditionRatio = Ratio.get(getWidth(), getHeight());
if (!Ratio.matches(renditionRatio, ratio)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(this.rendition.getPath())
.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != this.getClass()) {
return false;
}
RenditionMetadata other = (RenditionMetadata)obj;
return new EqualsBuilder()
.append(this.rendition.getPath(), other.rendition.getPath())
.build();
}
@Override
@SuppressWarnings("java:S3776") // ignore complexity
public int compareTo(RenditionMetadata obj) {
// always prefer the virtual rendition
boolean thisIsVirtualRendition = this instanceof VirtualTransformedRenditionMetadata;
boolean otherIsVirtualRendition = obj instanceof VirtualTransformedRenditionMetadata;
if (thisIsVirtualRendition && !otherIsVirtualRendition) {
return -2;
}
else if (otherIsVirtualRendition && !thisIsVirtualRendition) {
return 2;
}
// always prefer original rendition
boolean thisIsOriginalRendition = AssetRendition.isOriginal(getRendition());
boolean otherIsOriginalRendition = AssetRendition.isOriginal(obj.getRendition());
if (thisIsOriginalRendition && !otherIsOriginalRendition) {
return -1;
}
else if (otherIsOriginalRendition && !thisIsOriginalRendition) {
return 1;
}
// order by width, height, rendition path
Long thisWidth = getWidth();
Long otherWidth = obj.getWidth();
if (thisWidth.equals(otherWidth)) {
Long thisHeight = getHeight();
Long otherHeight = obj.getHeight();
if (thisHeight.equals(otherHeight)) {
String thisPath = getRendition().getPath();
String otherPath = obj.getRendition().getPath();
if (!StringUtils.equals(thisPath, otherPath)) {
// same width/height - compare paths as last resort
return thisPath.compareTo(otherPath);
}
else {
return 0;
}
}
else {
return thisHeight.compareTo(otherHeight);
}
}
else {
return thisWidth.compareTo(otherWidth);
}
}
@SuppressWarnings("null")
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
protected Layer getLayer() {
if (MediaFileType.isImage(getFileExtension())) {
return this.rendition.adaptTo(Resource.class).adaptTo(Layer.class);
}
else {
return null;
}
}
@SuppressWarnings("null")
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
protected InputStream getInputStream() {
return this.rendition.adaptTo(Resource.class).adaptTo(InputStream.class);
}
public @NotNull UriTemplate getUriTemplate(@NotNull UriTemplateType type, @NotNull DamContext damContext) {
if (!isImage() || isVectorImage()) {
throw new UnsupportedOperationException("Unable to build URI template for rendition: " + getRendition().getPath());
}
Dimension dimension = AssetRendition.getDimension(getRendition());
if (dimension == null) {
throw new IllegalArgumentException("Unable to get dimension for rendition: " + getRendition().getPath());
}
return new DamUriTemplate(type, dimension, rendition, null, null, Ratio.get(dimension), damContext);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.rendition.getPath());
if (getWidth() > 0 || getHeight() > 0) {
sb.append(" (").append(Long.toString(getWidth())).append("x").append(Long.toString(getHeight())).append(")");
}
return sb.toString();
}
/**
* Build media path and suffix. The suffix is url-encoded.
* @param mediaPath Media path
* @param suffix Suffix
* @return Media path and suffix
*/
static String buildMediaPath(String mediaPath, String suffix) {
return mediaPath + "/" + suffix;
}
@Override
@SuppressWarnings("unchecked")
public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
if (type == Rendition.class) {
return (AdapterType)this.rendition;
}
if (type == Resource.class) {
return (AdapterType)this.rendition.adaptTo(Resource.class);
}
else if (type == Layer.class) {
return (AdapterType)getLayer();
}
else if (type == InputStream.class) {
return (AdapterType)getInputStream();
}
return super.adaptTo(type);
}
/**
* @deprecated Prevent finalize attack (PMD CT_CONSTRUCTOR_THROW / SEI CERT Rule OBJ-11)
*/
@Override
@SuppressWarnings({ "PMD.EmptyFinalizer", "checkstyle:SuperFinalize", "checkstyle:NoFinalizerCheck", "java:S1113" })
@Deprecated(since = "2.0.0")
protected final void finalize() {
// do nothing
}
}