io.wcm.handler.mediasource.inline.InlineRendition Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of io.wcm.handler.media Show documentation
Show all versions of io.wcm.handler.media Show documentation
Media resolving, processing and markup generation.
/*
* #%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.inline;
import io.wcm.handler.media.CropDimension;
import io.wcm.handler.media.Dimension;
import io.wcm.handler.media.Media;
import io.wcm.handler.media.MediaArgs;
import io.wcm.handler.media.Rendition;
import io.wcm.handler.media.format.MediaFormat;
import io.wcm.handler.media.format.MediaFormatHandler;
import io.wcm.handler.media.impl.ImageFileServlet;
import io.wcm.handler.media.impl.JcrBinary;
import io.wcm.handler.media.impl.MediaFileServlet;
import io.wcm.handler.url.UrlHandler;
import io.wcm.sling.commons.adapter.AdaptTo;
import io.wcm.wcm.commons.caching.ModificationDate;
import io.wcm.wcm.commons.contenttype.FileExtension;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.adapter.Adaptable;
import org.apache.sling.api.adapter.SlingAdaptable;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.image.Layer;
/**
* {@link Rendition} implementation for inline media objects stored in a node in a content page.
*/
class InlineRendition extends SlingAdaptable implements Rendition {
private final Adaptable adaptable;
private final Resource resource;
private final Media media;
private final MediaArgs mediaArgs;
private final String fileName;
private final Dimension imageDimension;
private final String url;
private MediaFormat resolvedMediaFormat;
/**
* Special dimension instance that marks "scaling is required but not possible"
*/
private static final Dimension SCALING_NOT_POSSIBLE_DIMENSION = new Dimension(-1, -1);
/**
* @param resource Binary resource
* @param media Media metadata
* @param mediaArgs Media args
* @param fileName File name
*/
InlineRendition(Resource resource, Media media, MediaArgs mediaArgs, String fileName, Adaptable adaptable) {
this.resource = resource;
this.media = media;
this.mediaArgs = mediaArgs;
this.adaptable = adaptable;
// detect image dimension
String processedFileName = fileName;
// check if scaling is possible
String fileExtension = StringUtils.substringAfterLast(processedFileName, ".");
boolean isImage = FileExtension.isImage(fileExtension);
Dimension dimension = null;
Dimension scaledDimension = null;
if (isImage) {
// get dimension from image binary
dimension = getImageDimension();
// check if scaling is required
scaledDimension = getScaledDimension(dimension);
if (scaledDimension != null && !scaledDimension.equals(SCALING_NOT_POSSIBLE_DIMENSION)) {
// overwrite image dimension of {@link Rendition} instance with scaled dimensions
dimension = scaledDimension;
// change extension to JPEG because scaling always produces JPEG images
processedFileName = StringUtils.substringBeforeLast(processedFileName, ".") + "." + FileExtension.JPEG;
}
}
this.fileName = processedFileName;
this.imageDimension = dimension;
// build media url (it is null if no rendition is available for the given media args)
this.url = buildMediaUrl(scaledDimension);
// set first media format as resolved format - because only the first is supported
if (url != null && mediaArgs.getMediaFormats() != null && mediaArgs.getMediaFormats().length > 0) {
this.resolvedMediaFormat = mediaArgs.getMediaFormats()[0];
}
}
/**
* Gets the dimension of the uploaded image (if the binary is an image file at all).
* @return Dimension
*/
private Dimension getImageDimension() {
Dimension dimension = null;
// check for cropping dimension
if (this.media.getCropDimension() != null) {
dimension = this.media.getCropDimension();
}
else {
// if binary is image try to calculcate dimensions by loading it into a layer
Layer layer = this.resource.adaptTo(Layer.class);
if (layer != null) {
dimension = new Dimension(layer.getWidth(), layer.getHeight());
}
}
return dimension;
}
/**
* Checks if the current binary is an image and has to be scaled. In this case the destination dimension is returned.
* @return Scaled destination or null if no scaling is required. If a destination object with both
* width and height set to -1 is returned, a scaling is required but not possible with the given source
* object.
*/
private Dimension getScaledDimension(Dimension originalDimension) {
// check if image has to be rescaled
Dimension requestedDimension = getRequestedDimension();
boolean scaleWidth = (requestedDimension.getWidth() > 0
&& requestedDimension.getWidth() != originalDimension.getWidth());
boolean scaleHeight = (requestedDimension.getHeight() > 0
&& requestedDimension.getHeight() != originalDimension.getHeight());
if (scaleWidth || scaleHeight) {
long requestedWidth = requestedDimension.getWidth();
long requestedHeight = requestedDimension.getHeight();
// calculate missing width/height from ration if not specified
double imageRatio = (double)originalDimension.getWidth() / (double)originalDimension.getHeight();
if (requestedWidth == 0 && requestedHeight > 0) {
requestedWidth = (int)Math.round(requestedHeight * imageRatio);
}
else if (requestedWidth > 0 && requestedHeight == 0) {
requestedHeight = (int)Math.round(requestedWidth / imageRatio);
}
// calculate requested ratio
double requestedRatio = (double)requestedWidth / (double)requestedHeight;
// if ratio does not match, or requested width/height is larger than the original image scaling is not possible
if ((imageRatio > requestedRatio + MediaFormatHandler.RATIO_TOLERANCE)
|| (imageRatio < requestedRatio - MediaFormatHandler.RATIO_TOLERANCE)
|| (originalDimension.getWidth() < requestedWidth)
|| (originalDimension.getHeight() < requestedHeight)) {
return SCALING_NOT_POSSIBLE_DIMENSION;
}
else {
return new Dimension(requestedWidth, requestedHeight);
}
}
return null;
}
/**
* Build media URL for this rendition - either "native" URL to repository or virtual url to rescaled version.
* @return Media URL - null if no rendition is available
*/
private String buildMediaUrl(Dimension scaledDimension) {
// check for file extension filtering
if (!isMatchingFileExtension()) {
return null;
}
// check if image has to be rescaled
if (scaledDimension != null) {
// check if scaling is not possible
if (scaledDimension.equals(SCALING_NOT_POSSIBLE_DIMENSION)) {
return null;
}
// otherwise generate scaled image URL
return buildScaledMediaUrl(scaledDimension, this.media.getCropDimension());
}
// if no scaling but cropping required builid scaled image URL
if (this.media.getCropDimension() != null) {
return buildScaledMediaUrl(this.media.getCropDimension(), this.media.getCropDimension());
}
if (mediaArgs.isForceDownload()) {
// if not scaling and no cropping required but special content disposition headers required build download url
return buildDownloadMediaUrl();
}
else {
// if not scaling and no cropping required build native media URL
return buildNativeMediaUrl();
}
}
/**
* Builds "native" URL that returns the binary data directly from the repository.
* @return Media URL
*/
private String buildNativeMediaUrl() {
String path = null;
Resource parentResource = this.resource.getParent();
if (JcrBinary.isNtFile(parentResource)) {
// if parent resource is nt:file and its node name equals the detected filename, directly use the nt:file node path
if (StringUtils.equals(parentResource.getName(), getFileName())) {
path = parentResource.getPath();
}
// otherwise use nt:file node path with custom filename
else {
path = parentResource.getPath() + "./" + getFileName();
}
}
else {
// nt:resource node does not have a nt:file parent, use its path directly
path = this.resource.getPath() + "./" + getFileName();
}
// build externalized URL
UrlHandler urlHandler = AdaptTo.notNull(this.adaptable, UrlHandler.class);
return urlHandler.get(path).urlMode(this.mediaArgs.getUrlMode()).buildExternalResourceUrl(this.resource);
}
/**
* Builds URL to rescaled version of the binary image.
* @return Media URL
*/
private String buildScaledMediaUrl(Dimension dimension, CropDimension cropDimension) {
String resourcePath = this.resource.getPath();
// if parent resource is a nt:file resource, use this one as path for scaled image
Resource parentResource = this.resource.getParent();
if (JcrBinary.isNtFile(parentResource)) {
resourcePath = parentResource.getPath();
}
// URL to render scaled image via {@link InlineRenditionServlet}
String path = resourcePath + "." + ImageFileServlet.SELECTOR
+ "." + dimension.getWidth() + "." + dimension.getHeight()
+ (cropDimension != null ? "." + cropDimension.getCropString() : "")
+ (this.mediaArgs.isForceDownload() ? "." + MediaFileServlet.SELECTOR_DOWNLOAD : "")
+ "." + MediaFileServlet.EXTENSION + "/"
// replace extension based on the format supported by ImageFileServlet for rendering for this rendition
+ ImageFileServlet.getImageFileName(getFileName());
// build externalized URL
UrlHandler urlHandler = AdaptTo.notNull(this.adaptable, UrlHandler.class);
return urlHandler.get(path).urlMode(this.mediaArgs.getUrlMode()).buildExternalResourceUrl(this.resource);
}
/**
* Builds URL to rescaled version of the binary image.
* @return Media URL
*/
private String buildDownloadMediaUrl() {
String resourcePath = this.resource.getPath();
// if parent resource is a nt:file resource, use this one as path for scaled image
Resource parentResource = this.resource.getParent();
if (JcrBinary.isNtFile(parentResource)) {
resourcePath = parentResource.getPath();
}
// URL to render scaled image via {@link InlineRenditionServlet}
String path = resourcePath + "." + MediaFileServlet.SELECTOR
+ "." + MediaFileServlet.SELECTOR_DOWNLOAD
+ "." + MediaFileServlet.EXTENSION + "/" + getFileName();
// build externalized URL
UrlHandler urlHandler = AdaptTo.notNull(this.adaptable, UrlHandler.class);
return urlHandler.get(path).urlMode(this.mediaArgs.getUrlMode()).buildExternalResourceUrl(this.resource);
}
/**
* Checks if the file extension of the current binary matches with the requested extensions from the media args.
* @return true if file extension matches
*/
private boolean isMatchingFileExtension() {
String[] fileExtensions = mediaArgs.getFileExtensions();
if (fileExtensions == null || fileExtensions.length == 0) {
return true;
}
for (String fileExtension : fileExtensions) {
if (StringUtils.equalsIgnoreCase(fileExtension, getFileExtension())) {
return true;
}
}
return false;
}
/**
* Requested dimensions either from media format or fixed dimensions from media args.
* @return Requested dimensions
*/
private Dimension getRequestedDimension() {
// check for fixed dimensions from media args
if (mediaArgs.getFixedWidth() > 0 || mediaArgs.getFixedHeight() > 0) {
return new Dimension(mediaArgs.getFixedWidth(), mediaArgs.getFixedHeight());
}
// check for dimensions from mediaformat (evaluate only first media format)
MediaFormat[] mediaFormats = mediaArgs.getMediaFormats();
if (mediaFormats != null && mediaFormats.length > 0) {
Dimension dimension = mediaFormats[0].getMinDimension();
if (dimension != null) {
return dimension;
}
}
// fallback to 0/0 - no specific dimension requested
return new Dimension(0, 0);
}
@Override
public String getUrl() {
return this.url;
}
@Override
public String getPath() {
return this.resource.getPath();
}
@Override
public String getFileName() {
return this.fileName;
}
@Override
public String getFileExtension() {
return StringUtils.substringAfterLast(this.fileName, ".");
}
@Override
public long getFileSize() {
Node node = this.resource.adaptTo(Node.class);
if (node != null) {
try {
Property data = node.getProperty(JcrConstants.JCR_DATA);
return data.getBinary().getSize();
}
catch (RepositoryException ex) {
throw new RuntimeException("Unable to detect binary file size for " + this.resource.getPath(), ex);
}
}
else {
// fallback to Sling API if JCR node is not present (inefficient - but this should happen only in unit tests)
try {
InputStream is = this.resource.getValueMap().get(JcrConstants.JCR_DATA, InputStream.class);
return IOUtils.toByteArray(is).length;
}
catch (IOException ex) {
throw new RuntimeException("Unable to detect binary file size for " + this.resource.getPath(), ex);
}
}
}
@Override
public String getMimeType() {
return JcrBinary.getMimeType(this.resource);
}
@Override
public Date getModificationDate() {
return ModificationDate.get(this.resource);
}
@Override
public MediaFormat getMediaFormat() {
return resolvedMediaFormat;
}
@Override
public ValueMap getProperties() {
return this.resource.getValueMap();
}
@Override
public boolean isImage() {
return FileExtension.isImage(getFileExtension());
}
@Override
public boolean isFlash() {
return FileExtension.isFlash(getFileExtension());
}
@Override
public boolean isDownload() {
return !isImage() && !isFlash();
}
@Override
public long getWidth() {
if (this.imageDimension != null) {
return this.imageDimension.getWidth();
}
else {
return 0;
}
}
@Override
public long getHeight() {
if (this.imageDimension != null) {
return this.imageDimension.getHeight();
}
else {
return 0;
}
}
@Override
@SuppressWarnings("unchecked")
public AdapterType adaptTo(Class type) {
if (type == Resource.class) {
return (AdapterType)this.resource;
}
else if (type == Layer.class && isImage()) {
return (AdapterType)this.resource.adaptTo(Layer.class);
}
else if (type == InputStream.class) {
return (AdapterType)this.resource.adaptTo(InputStream.class);
}
return super.adaptTo(type);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy