InlineMediaSource.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.inline;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.adapter.Adaptable;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.commons.mime.MimeTypeService;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.annotation.versioning.ProviderType;
import com.day.cq.commons.jcr.JcrConstants;
import io.wcm.handler.commons.dom.HtmlElement;
import io.wcm.handler.media.Asset;
import io.wcm.handler.media.Media;
import io.wcm.handler.media.MediaArgs;
import io.wcm.handler.media.MediaInvalidReason;
import io.wcm.handler.media.MediaNameConstants;
import io.wcm.handler.media.MediaRequest;
import io.wcm.handler.media.impl.JcrBinary;
import io.wcm.handler.media.spi.MediaHandlerConfig;
import io.wcm.handler.media.spi.MediaSource;
import io.wcm.sling.commons.util.Escape;
/**
* Default implementation for media references to binaries stored in a node inside the content page.
*/
@Model(adaptables = {
SlingHttpServletRequest.class, Resource.class
})
@ProviderType
public final class InlineMediaSource extends MediaSource {
@Self
private Adaptable adaptable;
@Self
private MediaHandlerConfig mediaHandlerConfig;
@OSGiService(injectionStrategy = InjectionStrategy.OPTIONAL)
private MimeTypeService mimeTypeService;
/**
* Media source ID
*/
public static final @NotNull String ID = "inline";
@Override
public @NotNull String getId() {
return ID;
}
@Override
public boolean accepts(@NotNull MediaRequest mediaRequest) {
// if no media source id is defined fallback to auto-detection of inline media object in resource
String mediaSourceId = mediaRequest.getResourceProperties().get(MediaNameConstants.PN_MEDIA_SOURCE, String.class);
if (StringUtils.isEmpty(mediaSourceId)) {
// accept for inline media if "mediaInline" child node is present
return getMediaInlineResource(mediaRequest) != null;
}
else {
return super.accepts(mediaRequest);
}
}
@Override
public boolean accepts(@Nullable String mediaRef) {
// not supported
return false;
}
@Override
public @Nullable String getPrimaryMediaRefProperty() {
// not supported
return null;
}
@Override
public @NotNull Media resolveMedia(@NotNull Media media) {
MediaArgs mediaArgs = media.getMediaRequest().getMediaArgs();
// the resource that was referenced originally (and may contain additional attributes)
Resource referencedResource = media.getMediaRequest().getResource();
Resource ntFileResource = null;
Resource ntResourceResource = null;
// get and check resource holding binary data (with primary node type nt:resource)
Resource mediaInlineResource = getMediaInlineResource(media.getMediaRequest());
if (mediaInlineResource != null) {
if (JcrBinary.isNtFile(mediaInlineResource)) {
ntFileResource = mediaInlineResource;
ntResourceResource = mediaInlineResource.getChild(JcrConstants.JCR_CONTENT);
}
else if (JcrBinary.isNtResource(mediaInlineResource)) {
ntResourceResource = mediaInlineResource;
}
}
// skip further processing if nor binary resource found
if (referencedResource == null || ntResourceResource == null) {
media.setMediaInvalidReason(MediaInvalidReason.MEDIA_REFERENCE_INVALID);
return media;
}
// Update media args settings from resource (e.g. alt. text setings)
updateMediaArgsFromResource(mediaArgs, referencedResource, mediaHandlerConfig);
// Check for transformations
media.setCropDimension(getMediaCropDimension(media.getMediaRequest(), mediaHandlerConfig));
media.setRotation(getMediaRotation(media.getMediaRequest(), mediaHandlerConfig));
media.setMap(getMediaMap(media.getMediaRequest(), mediaHandlerConfig));
// detect and clean up file name
String fileName = detectFileName(referencedResource, ntFileResource, ntResourceResource);
fileName = cleanupFileName(fileName);
// generate media item and rendition for inline media
Asset asset = getInlineAsset(ntResourceResource, media, fileName);
media.setAsset(asset);
// resolve rendition
boolean renditionsResolved = resolveRenditions(media, asset, mediaArgs);
// set media invalid reason
if (!renditionsResolved) {
if (media.getRenditions().isEmpty()) {
media.setMediaInvalidReason(MediaInvalidReason.NO_MATCHING_RENDITION);
}
else {
media.setMediaInvalidReason(MediaInvalidReason.NOT_ENOUGH_MATCHING_RENDITIONS);
}
}
return media;
}
/**
* Get implementation of inline media item
* @param ntResourceResource nt:resource node
* @param media Media metadata
* @param fileName File name
* @return Inline media item instance
*/
private Asset getInlineAsset(Resource ntResourceResource, Media media, String fileName) {
return new InlineAsset(ntResourceResource, media, mediaHandlerConfig, fileName, adaptable);
}
/**
* Detect filename for inline binary.
* @param referencedResource Resource that was referenced in media reference and may contain file name property.
* @param ntFileResource nt:file resource (optional, null if not existent)
* @param ntResourceResource nt:resource resource
* @return Detected or virtual filename. Never null.
*/
private String detectFileName(@NotNull Resource referencedResource, @Nullable Resource ntFileResource,
@Nullable Resource ntResourceResource) {
// detect file name
String fileName = null;
// if referenced resource is not the nt:file node check for <nodename>Name property
if (ntFileResource != null && !referencedResource.equals(ntFileResource)) {
fileName = referencedResource.getValueMap().get(ntFileResource.getName() + "Name", String.class);
}
// if not nt:file node exists and the referenced resource is not the nt:resource node check for <nodename>Name property
else if (ntFileResource == null && ntResourceResource != null && !referencedResource.equals(ntResourceResource)) {
fileName = referencedResource.getValueMap().get(ntResourceResource.getName() + "Name", String.class);
}
// otherwise use node name of nt:file resource if it exists
else if (ntFileResource != null) {
fileName = ntFileResource.getName();
}
// make sure filename has an extension, otherwise build virtual file name
if (!StringUtils.contains(fileName, ".")) {
fileName = null;
}
// if no filename found detect extension from mime type and build virtual filename
if (StringUtils.isBlank(fileName)) {
String fileExtension = null;
if (ntResourceResource != null) {
String mimeType = ntResourceResource.getValueMap().get(JcrConstants.JCR_MIMETYPE, String.class);
if (StringUtils.isNotEmpty(mimeType) && mimeTypeService != null) {
fileExtension = mimeTypeService.getExtension(mimeType);
}
}
if (StringUtils.isEmpty(fileExtension)) {
fileExtension = "bin";
}
fileName = "file." + fileExtension;
}
return fileName;
}
/**
* Make sure filename contains no invalid characters or path parts
* @param fileName File name
* @return Cleaned up file name
*/
private String cleanupFileName(String fileName) {
String processedFileName = fileName;
// strip off path parts
if (StringUtils.contains(processedFileName, "/")) {
processedFileName = StringUtils.substringAfterLast(processedFileName, "/");
}
if (StringUtils.contains(processedFileName, "\\")) {
processedFileName = StringUtils.substringAfterLast(processedFileName, "\\");
}
// make sure filename does not contain any invalid characters
processedFileName = Escape.validFilename(processedFileName);
return processedFileName;
}
/**
* Get resource with media inline data (nt:file node).
* @param mediaRequest Media reference
* @return Resource or null if not present
*/
private Resource getMediaInlineResource(MediaRequest mediaRequest) {
Resource resource = mediaRequest.getResource();
if (resource == null) {
return null;
}
// check if resource itself is a nt:file node
if (JcrBinary.isNtFileOrResource(resource)) {
return resource;
}
// check if child node exists which is a nt:file node
String refProperty = StringUtils.defaultString(mediaRequest.getMediaPropertyNames().getRefProperty(),
mediaHandlerConfig.getMediaInlineNodeName());
Resource mediaInlineResource = resource.getChild(refProperty);
if (JcrBinary.isNtFileOrResource(mediaInlineResource)) {
return mediaInlineResource;
}
// not found
return null;
}
@Override
public void enableMediaDrop(@NotNull HtmlElement element, @NotNull MediaRequest mediaRequest) {
// not supported
}
@Override
public String toString() {
return ID;
}
}