com.day.cq.dam.core.process.CreateWebEnabledImageProcess 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 static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT;
import static com.day.cq.commons.jcr.JcrConstants.JCR_LASTMODIFIED;
import static com.day.cq.commons.jcr.JcrConstants.JCR_LAST_MODIFIED_BY;
import static com.day.cq.dam.api.DamConstants.PREFIX_ASSET_THUMBNAIL;
import static com.day.cq.dam.api.DamConstants.PREFIX_ASSET_WEB;
import static com.day.cq.dam.core.process.CreateThumbnailProcess.isThumbnailStale;
import java.util.Calendar;
import java.util.List;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import com.day.cq.dam.commons.util.DamUtil;
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 com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.DamConstants;
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.commons.process.AbstractAssetWorkflowProcess;
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;
/**
* The CreateWebEnabledImageProcess
is called in a Workflow {@link com.day.cq.workflow.exec.WorkflowProcess Process} step. This
* Process creates, if possible, a web enabled representation from the {@link com.day.cq.dam.api.Asset Asset}.
* The Format of the webenabled image, can be set by arguments to the
* {@link com.day.cq.workflow.exec.WorkflowProcess#execute(WorkItem, WorkflowSession, MetaDataMap) Process}
* Process is memory aware. If the required memory is not available, image creatation is deferred or cancelled if the memory requirement
* can't be sattisfied within a fixed amount of trails.
*
* Example with the following workflow step arguments:
*
*
* dimension:500:100,
* quality:60
*
*
* The Process create a PNG Image of the size of 500x100 Pixels. The Quality will be set to 60%.
*
* Arguments:
*
*
*
* Prefix
* Description
* Default
* Multiple
* Example
*
*
*
* dimension:
* Amount in pixels given in the following format widht:hight.
* Image will be risized to have at most this dimension, keeping aspect ratio
* 1000:1000
*
* dimension:400:300
*
*
* quality:
* Quality as percentage from optimal. This depends on the mimetype of the resulting image. For Gifs this reduces the amount of colors,
* for Jpgs this sets the compression rate, etc.
* 60
*
* quality:90
*
*
* skip:
* Set the mimetypes of Assets that should not be processed
*
* mulitple
* skip:application/pdf, skip:image/tiff
*
*
* mimetype:
* Set the mimetype of the Image to create
* image/png
*
* mimetype:image/jpg
*
*
* keepFormatList:
* Commas epareted list of mimetypes that can be taken webinabled image
* image/pjpeg, image/jpeg, image/jpg, image/gif, image/png, image/x-png
*
* keepFormatList:image/gif,image/png,image/x-png
*
*
*
* @see AbstractAssetWorkflowProcess
*/
@Component(metatype = false)
@Service
@Property(name = "process.label", value = "Create Web Enabled Image")
public class CreateWebEnabledImageProcess extends AbstractAssetWorkflowProcess {
private static final Logger log = LoggerFactory.getLogger(CreateWebEnabledImageProcess.class);
@Reference
protected RenditionMaker renditionMaker;
/**
* Enum mixing old and new metadata argument names.
*/
public enum Arguments {
PROCESS_ARGS("PROCESS_ARGS"), // legacy metadata property holding string with all arguments
DIMENSION("dimension"), // legacy only
WIDTH("width"), HEIGHT("height"), QUALITY("quality"), MIME_TYPE("mimetype"), KEEP_FORMAT_LIST("keepFormatList"), SKIP("skip");
public final String legacyName;
Arguments(String legacyName) {
this.legacyName = legacyName;
}
}
/** The parsed configuration for this wf process */
public static class Config {
public int width;
public int height;
public int quality;
public String mimeType;
public String[] mimeTypesToKeep;
public String[] skipMimeTypes;
}
public void createWebEnabledImage(WorkItem workItem, Config config, Asset asset, RenditionMaker renditionMaker)
throws RepositoryException {
if (handleAsset(asset, config)) {
asset.setBatchMode(true);
// skip rendition if it exists
if(isWebThumbnailStale(asset, config.width, config.height)) {
RenditionTemplate template = renditionMaker.createWebRenditionTemplate(asset, config.width, config.height, config.quality,
config.mimeType, config.mimeTypesToKeep);
renditionMaker.generateRenditions(asset, template);
}
}
// lastModifiedBy is admin user by renditionMaker.generateRenditions
// set lastModifiedBy appropriately
final Node assetNode = asset.adaptTo(Node.class);
final Node content = assetNode.getNode(JCR_CONTENT);
// get user id
String resolvedUser = workItem.getWorkflowData().getMetaDataMap().get("userId", String.class);
Rendition rendition = asset.getRendition(DamConstants.ORIGINAL_FILE);
if (rendition != null) {
String lastModified = (String) rendition.getProperties().get(JCR_LAST_MODIFIED_BY);
// It can be blank in cases where there was no last modified for
// the original
if (StringUtils.isNotBlank(lastModified)) {
resolvedUser = lastModified;
}
}
content.setProperty(JCR_LAST_MODIFIED_BY, resolvedUser);
content.setProperty(JCR_LASTMODIFIED, Calendar.getInstance());
}
private boolean isWebThumbnailStale(Asset asset, int width, int height) {
// check both png & jpeg mime types
String pngName = PREFIX_ASSET_WEB + "." + String.valueOf(width) + "." + String.valueOf(height) + ".png";
String jpegName = PREFIX_ASSET_WEB + "." + String.valueOf(width) + "." + String.valueOf(height) + ".jpeg";
// check both for existing fresh thumbnails
return (isThumbnailStale(asset, pngName) && isThumbnailStale(asset, jpegName)) ;
}
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 web enabled image, asset [{" + wfPayload
+ "}] in payload doesn't exist for workflow [{" + workItem.getId() + "}].";
throw new WorkflowException(message);
}
final Config config = parseConfig(metaData);
try {
createWebEnabledImage(workItem, config, asset, renditionMaker);
} catch (RepositoryException re) {
throw new WorkflowException(re);
}
}
public Config parseConfig(MetaDataMap metaData) {
// check for legacy config first
String processArgs = metaData.get(Arguments.PROCESS_ARGS.name(), String.class);
if (StringUtils.isNotEmpty(processArgs)) {
return parseLegacyConfig(processArgs);
}
Config cfg = new Config();
// only long values can be automatically converted from strings (jcr LONG), not integer
cfg.width = metaData.get(Arguments.WIDTH.name(), 1000L).intValue();
cfg.height = metaData.get(Arguments.HEIGHT.name(), 1000L).intValue();
cfg.mimeType = metaData.get(Arguments.MIME_TYPE.name(), "image/png");
cfg.quality = metaData.get(Arguments.QUALITY.name(), cfg.mimeType.equals("image/gif") ? 256L : 60L).intValue();
cfg.mimeTypesToKeep = metaData.get(Arguments.KEEP_FORMAT_LIST.name(), new String[] { "image/pjpeg", "image/jpeg", "image/jpg",
"image/gif", "image/png", "image/x-png" });
cfg.skipMimeTypes = metaData.get(Arguments.SKIP.name(), new String[] {});
return cfg;
}
private Config parseLegacyConfig(String processArgs) {
Config cfg = new Config();
String[] args = processArgs.split(",");
// dimension
final String dimension = getFirstValueFromArgs(args, Arguments.DIMENSION.legacyName, null);
if (dimension == null) {
cfg.width = 1000;
cfg.height = 1000;
} else {
String dim[] = dimension.split(":");
cfg.width = Integer.valueOf(dim[0]);
cfg.height = Integer.valueOf(dim[1]);
}
// default mime type
cfg.mimeType = getFirstValueFromArgs(args, Arguments.MIME_TYPE.legacyName, "image/png");
// keep the original format?
String keepFormat = getFirstValueFromArgs(args, Arguments.KEEP_FORMAT_LIST.legacyName,
"image/pjpeg,image/jpeg,image/jpg,image/gif,image/png,image/x-png");
cfg.mimeTypesToKeep = keepFormat.split(",");
// image quality: from 0 to 100 % in case of jpeg or number of colors 0 to 256 in case of gif
String qualityStr = getFirstValueFromArgs(args, Arguments.QUALITY.legacyName, cfg.mimeType.equals("image/gif") ? "256" : "60");
cfg.quality = Integer.valueOf(qualityStr);
// mime types to ignore for this process
List values = getValuesFromArgs(Arguments.SKIP.legacyName, args);
cfg.skipMimeTypes = values.toArray(new String[values.size()]);
return cfg;
}
private String getFirstValueFromArgs(String[] arguments, String key, String defaultValue) {
List values = getValuesFromArgs(key, arguments);
if (values.size() > 0) {
return values.get(0);
} else {
return defaultValue;
}
}
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;
}
}