com.day.cq.dam.core.process.CreateThumbnailProcess Maven / Gradle / Ivy
/*
* Copyright 1997-2008 Day Management AG
* Barfuesserplatz 6, 4001 Basel, Switzerland
* All Rights Reserved.
*
* This software is the confidential and proprietary information of
* Day Management AG, ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Day.
*/
package com.day.cq.dam.core.process;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.Rendition;
import com.day.cq.dam.api.renditions.RenditionMaker;
import com.day.cq.dam.api.renditions.RenditionTemplate;
import com.day.cq.dam.api.thumbnail.ThumbnailConfig;
import com.day.cq.dam.commons.process.AbstractAssetWorkflowProcess;
import com.day.cq.dam.commons.thumbnail.ThumbnailConfigImpl;
import com.day.cq.dam.commons.util.DamUtil;
import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.metadata.MetaDataMap;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* The CreateThumbnailProcess
is called in a Workflow process step. This process will create one or more Thumbnails for the
* Asset to be procesed. The Thumbnail generation is delegated to the appropriate {@link com.day.cq.dam.api.handler.AssetHandler
* AssetHandler}. The thumbails Dimensions are given as the Workflow
* {@link com.day.cq.workflow.exec.WorkflowProcess#execute(WorkItem, WorkflowSession, MetaDataMap) Process arguments}
* Each argument is considered as one thumbnail configuration. The configuration has to be enclosed in squared brackets ([]) The
* configuration consists of a number for the width, the height in pixel and an optional flag that indicates if the image should be
* centered.
* These values are seperated by a colon character (:).
* Dimension:
* The thumbnail from an image will be created by resizeing according the thumbnail configuration. The ascpect ratio is respected and the
* image will have at most the dimension given by the configuration.
* Centering:
* If the center flag is set to true, the thumbnail image will have exactly the size given by the configuraiton. If the resized image
* is smaller it will be centered within the thumbnail.
*
* Example with the following Workflow step arguments:
*
*
* [70:70],
* [80:100:true]
*
*
* The Process creates two PNG images one of the size of 70x70 pixel and one 80x100 where the result is centred
*/
@Component
@Service
@Property(name = "process.label", value = "Create Thumbnail")
public class CreateThumbnailProcess extends AbstractAssetWorkflowProcess {
/**
* Logger instance for this class.
*/
private static final Logger log = LoggerFactory.getLogger(CreateThumbnailProcess.class);
/**
* The available arguments to this process implementation.
*/
public enum Arguments {
PROCESS_ARGS, CONFIGS, SKIP_MIME_TYPES
}
/** The parsed configuration for this wf process */
public static class Config {
public String[] skipMimeTypes;
public ThumbnailConfig[] thumbnails;
public Config() {
}
public Config(String[] skipMimeTypes, ThumbnailConfig[] thumbnails) {
this.skipMimeTypes = skipMimeTypes;
this.thumbnails = thumbnails;
}
}
@Reference
private RenditionMaker renditionMaker;
public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metaData) throws WorkflowException {
final Asset asset = getAssetFromPayload(workItem, workflowSession.getSession());
if (asset == null) {
String wfPayload = workItem.getWorkflowData().getPayload().toString();
String message = "execute: cannot create thumbnails, asset [{" + wfPayload + "}] in payload doesn't exist for workflow [{"
+ workItem.getId() + "}].";
throw new WorkflowException(message);
}
final Config config = parseConfig(metaData);
createThumbnails(asset, config, renditionMaker);
}
public void createThumbnails(Asset asset, Config config, RenditionMaker renditionMaker) {
if (handleAsset(asset, config)) {
asset.setBatchMode(true);
// for each thumbnail config, create a rendition template
RenditionTemplate[] templates = createRenditionTemplates(asset, config.thumbnails, renditionMaker);
// create thumbnail renditions
renditionMaker.generateRenditions(asset, templates);
}
}
public static RenditionTemplate[] createRenditionTemplates(Asset asset, ThumbnailConfig[] thumbnails, RenditionMaker renditionMaker) {
ArrayList list = new ArrayList();
for (int i = 0; i < thumbnails.length; i++) {
ThumbnailConfig thumb = thumbnails[i];
// filter thumbnails that have been updated
String thName = DamUtil.getThumbnailName(thumb.getWidth(), thumb.getHeight(), thumb.doCenter() ? new String[]{"margin"} : null);
if(isThumbnailStale(asset, thName)) {
list.add(renditionMaker.createThumbnailTemplate(asset, thumb.getWidth(), thumb.getHeight(), thumb.doCenter()));
}
}
return list.toArray(new RenditionTemplate[list.size()]);
}
/**
* Check if the rendition present has been updated. If yes then return true, false if it needs to be updated
*/
static boolean isThumbnailStale(Asset asset, String thumb) {
// 1. check against all renditions
Rendition rendition = asset.getRendition(thumb);
Rendition original = asset.getOriginal();
// 2. new rendition is not needed
if(isNotNull(rendition, original) &&
rendition.getResourceMetadata().getModificationTime() > original.getResourceMetadata().getModificationTime()) {
return false ;
}
// 3. template generates a new rendition
return true;
}
private static boolean isNotNull(Rendition rendition, Rendition rendition2) {
return (rendition != null && rendition.getResourceMetadata() != null
&& rendition2 != null && rendition2.getResourceMetadata() != null);
}
public Config parseConfig(MetaDataMap metaData) {
Config cfg = new Config();
String[] args;
String[] skipMimeTypeArgs;
// the 'old' way, ensures backward compatibility
String processArgs = metaData.get(Arguments.PROCESS_ARGS.name(), String.class);
if (processArgs != null && !processArgs.equals("")) {
String[] argTypes = processArgs.split(";");
if (argTypes.length > 1) {
skipMimeTypeArgs = argTypes[1].split(",");
args = argTypes[0].split(",");
} else {
skipMimeTypeArgs = null;
args = processArgs.split(",");
}
if (skipMimeTypeArgs != null) {
// must be prefixed with "skip:"
List values = getValuesFromArgs("skip", skipMimeTypeArgs);
cfg.skipMimeTypes = values.toArray(new String[values.size()]);
}
}
// the 'new' way
else {
//
String[] configs = metaData.get(Arguments.CONFIGS.name(), String[].class);
skipMimeTypeArgs = metaData.get(Arguments.SKIP_MIME_TYPES.name(), String[].class);
if (configs != null) {
args = configs;
} else {
args = new String[0];
}
if (skipMimeTypeArgs != null) {
// can be prefixed with "skip:", but it's redundant and it's easier without
List values = new ArrayList();
for (String arg : skipMimeTypeArgs) {
values.add(StringUtils.removeStart(arg, "skip:"));
}
cfg.skipMimeTypes = values.toArray(new String[values.size()]);
}
}
cfg.thumbnails = parseThumbnailArguments(args);
return cfg;
}
public static ThumbnailConfig[] parseThumbnailArguments(String[] args) {
List list = new ArrayList();
for (String arg : args) {
ThumbnailConfig config = parseThumbnailArguments(arg);
if (config != null) {
list.add(config);
}
}
return list.toArray(new ThumbnailConfig[list.size()]);
}
public static ThumbnailConfig parseThumbnailArguments(final String arg) {
// remove any whitespace
String str = arg.trim();
// if square brackets are present, just use the inside
if (str.contains("[")) {
str = StringUtils.substringBetween(str, "[", "]");
// if no closing bracket
if (str == null) {
log.warn("parseConfig: cannot parse width/height, missing closing bracket '{}'.", arg);
return null;
}
}
final String fragments[] = str.split(":");
// ensure sufficient arguments are present (at least width:height)
if (fragments.length >= 2) {
try {
final Integer width = Integer.valueOf(fragments[0]);
final Integer height = Integer.valueOf(fragments[1]);
boolean doCenter = false;
if (fragments.length > 2) {
doCenter = Boolean.valueOf(fragments[2]);
}
return new ThumbnailConfigImpl(width, height, doCenter);
} catch (NumberFormatException e) {
log.warn("parseConfig: cannot parse, invalid width/height specified in config '{}': ", str, e);
return null;
}
} else {
log.warn("parseConfig: cannot parse, insufficient arguments in config '{}'.", str);
return null;
}
}
protected boolean handleAsset(Asset asset, Config config) {
if (asset == null || config.skipMimeTypes == null) {
return true;
}
final String mimeType = asset.getMimeType();
if (mimeType == null) {
// absence of mimetype is handled at later stage in process
return true;
}
for (String val : config.skipMimeTypes) {
if (mimeType.matches(val)) {
log.debug(this.getClass().getName() + " skipped for MIME type: " + mimeType);
return false;
}
}
return true;
}
}