All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.cq.dam.core.process.WaterMarkProcess Maven / Gradle / Ivy

package com.day.cq.dam.core.process;

import com.day.cq.commons.ImageHelper;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.commons.jcr.JcrUtil;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.Rendition;
import com.day.cq.dam.commons.process.AbstractAssetWorkflowProcess;
import com.day.cq.dam.commons.util.DamUtil;
import com.day.cq.dam.commons.util.MemoryUtil;
import com.day.cq.dam.commons.watermark.*;
import com.day.cq.dam.commons.watermark.Font;
import com.day.cq.i18n.I18n;
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 com.day.image.Layer;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.*;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.jackrabbit.util.Text;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.commons.mime.MimeTypeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.IIOException;
import javax.jcr.*;
import javax.jcr.version.VersionManager;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 *
 */
@Component(metatype = false)
@Service
@Property(name = "process.label", value = "Image Watermarking Process")
public class WaterMarkProcess extends AbstractAssetWorkflowProcess {

    /**
     * Logger instance for this class.
     */
    private static final Logger log = LoggerFactory.getLogger(WaterMarkProcess.class);
    private static final String WATERMARKED_RENDITION_NAME = "cq.dam.wm.";

    /**
     * The available arguments to this process implementation.
     */
    public enum Arguments {
        PROCESS_ARGS("PROCESS_ARGS"),
        TEXT("text"),
        SIZE("size"),
        COLOR("color"),
        POSITION("position"),
        OPACITY("opacity"),
        ARCHIVE("archive"),
        IMAGE("image"),
        ORIENTATION("orientation"),
        FONT("font"),
        VERTICAL("vertical"),
        USE_METADATA("useMetadata");

        private String argumentName;

        Arguments(String argumentName) {
            this.argumentName = argumentName;
        }

        public String getArgumentName() {
            return this.argumentName;
        }

        public String getArgumentPrefix() {
            return this.argumentName + ":";
        }

    }

    public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metaData) throws WorkflowException {
        String[] args = buildArguments(metaData);
        final Session session = workflowSession.getSession();
        final Asset wfAsset = getAssetFromPayload(workItem, session);
        Iterator assets = null;
        List assetsList = new ArrayList();

        //Terminate the process if asset not found in workflow item payload
        if (wfAsset == null) {
            String wfPayload = workItem.getWorkflowData().getPayload().toString();

            try {
                final Resource resource = getResourceResolver(workflowSession.getSession()).getResource(wfPayload);
                if (null != resource) {
                    assets = DamUtil.getAssets(resource);
                }
            }
            catch(Exception e) {
                String message = "execute: cannot create web enabled image, asset [{" + wfPayload + "}] in payload doesn't exist for workflow [{" + workItem.getId() + "}].";
                throw new WorkflowException(message);
            }
        }
        else {
            assetsList.add(wfAsset);
            assets = assetsList.iterator();
        }

        if(assets != null) {
            while (assets.hasNext()) {
                Asset asset = assets.next();
                try {
                    if (isWatermarkable(asset)) {
                        //Check if system resources can load asset image into memory
                        if (MemoryUtil.hasEnoughSystemMemory(asset)) {
                            // retry to load image if there is not enough memory
                            // currently
                            // available.
                            // did not find a more elegant solution by now...
                            boolean isLoaded = false;
                            long maxTrials = 100;
                            while (!isLoaded && maxTrials > 0) {
                                try {
                                    log.info("Begin water marking [{}]", asset.getName());
                                    createWatermark(workflowSession, asset, args);
                                    isLoaded = true;
                                } catch (Exception e) {
                                    if (e instanceof IIOException && e.getMessage().contains("Not enough memory")) {
                                        isLoaded = false;
                                        maxTrials--;
                                        log.debug("execute: insufficient memory, reloading image. Free mem [{}]. Asset [{}].",
                                                Runtime.getRuntime().freeMemory(), asset.getPath());
                                        // sleep at least 1250ms and maximal 3750ms
                                        Thread.sleep((long) (2500 * (Math.random() + 0.5)));
                                    } else {
                                        log.error("execute: error while watermarking image for [{}]: ", asset.getPath(), e);
                                        throw new WatermarkingException(e.getMessage());
                                    }
                                }
                            }
                            if (maxTrials == 0) {
                                log.warn("execute: failed creating thumbnails, insufficient memory even after [{}] trials for [{}].",
                                        100, asset.getPath());
                            }
                        } else {
                            log.warn(
                                    "execute: failed loading image, insufficient memory. Increase heap size up to [{}bytes] for asset [{}].",
                                    MemoryUtil.suggestMaxHeapSize(asset), asset.getPath());
                        }
                    }
                    else {
                        log.info("not supporting watermarking of any asset other than images. no-op.");
                    }
                } catch (Exception e) {
                    log.error("execute: error while watermarking asset; work item [{}]: ", workItem.getId(), e);
                    log.error(e.getMessage());
                    throw new WorkflowException(e);
                }
            }
        }
    }

    // ------------< helpers >--------------------------------------------------

    /**
     * Currently only images can be watermarked.
     *
     * @param asset
     * @return
     */
    private boolean isWatermarkable(Asset asset) {
        String assetMimeType = asset.getMimeType();
        return assetMimeType.contentEquals("image/jpeg") || assetMimeType.contentEquals("image/png");
    }

    private void createWatermark(WorkflowSession workflowSession, Asset asset, String[] args) throws WatermarkingException {
        final Rendition original = asset.getOriginal();
        Node assetNode = null;

        Layer layer = ImageHelper.createLayer(original);

        // if image is still null, try with web rendition. it can be
        // null in cases where the original is not image
        if (layer == null) {
            layer = ImageHelper.createLayer(getThumbnail(asset));
        }

        if(layer == null) {
            throw new WatermarkingException("Unable to create layer from asset image");
        }

        //-----------------------------------------Begin processing parameters-------------------------------//
        String text = TextWatermark.COPYRIGHT;
        int size = Font.DEFAULT_SIZE;
        Color color = Font.DEFAULT_COLOR;
        Location position = Watermark.DEFAULT_LOCATION;
        float opacity = Watermark.DEFAULT_OPACITY;
        boolean archive = false;
        double orientation = Watermark.DEFAULT_ORIENTATION;
        String font = Font.DEFAULT_FAMILY;
        boolean vertical = false;
        boolean useMetadata = true;

        //extract process parameters from args
        List values = getValuesFromArgs(Arguments.TEXT.getArgumentName(), args);
        if(values != null && !values.isEmpty()) {
            text = values.get(0);
        }
        else {
            //throw new WatermarkingException("Watermarking TEXT missing");
        }

        values = getValuesFromArgs(Arguments.SIZE.getArgumentName(), args);
        if(values != null && !values.isEmpty()) {
            try {
                size = Integer.parseInt(values.get(0));
            } catch (NumberFormatException e) {
                log.warn("Invalid value for font size. Using default {}.");
            }
        }

        values = getValuesFromArgs(Arguments.COLOR.getArgumentName(), args);
        if(values != null && !values.isEmpty()) {
            try {
                //Generate java color object from Hex code
                color = Color.decode("0x"+values.get(0));
            } catch (NumberFormatException e) {
                log.warn("Invalid value for font color. Using default {}.", color);
            }
        }

        values = getValuesFromArgs(Arguments.POSITION.getArgumentName(), args);
        if(values != null && !values.isEmpty()) {
            try {
                position = Location.valueOf(values.get(0));
            } catch (IllegalArgumentException e) {
                log.warn("Invalid value for watermark position. Using default {}.", position.name());
            }
        }

        values = getValuesFromArgs(Arguments.OPACITY.getArgumentName(), args);
        if(values != null && !values.isEmpty()) {
            try {
                opacity = Float.parseFloat(values.get(0))/100;
            } catch (NumberFormatException e) {
                log.warn("Invalid value for watermark opacity. Using default {}.", opacity);
            }
        }

        values = getValuesFromArgs(Arguments.ARCHIVE.getArgumentName(), args);
        if(values != null && !values.isEmpty()) {
            archive = Boolean.parseBoolean(values.get(0));
        }

        values = getValuesFromArgs(Arguments.ORIENTATION.getArgumentName(), args);
        if(values != null && !values.isEmpty()) {
            try {
                orientation = Double.parseDouble(values.get(0));
            } catch (NumberFormatException e) {
                log.warn("Invalid value for watermark orientation. Using default {}.", orientation);
            }
        }

        values = getValuesFromArgs(Arguments.FONT.getArgumentName(), args);
        if(values != null && !values.isEmpty()) {
            font = values.get(0);
        }

        values = getValuesFromArgs(Arguments.VERTICAL.getArgumentName(), args);
        if(values != null && !values.isEmpty()) {
            vertical = Boolean.parseBoolean(values.get(0));
        }

        values = getValuesFromArgs(Arguments.USE_METADATA.getArgumentName(), args);
        if(values != null && !values.isEmpty()) {
            useMetadata = Boolean.parseBoolean(values.get(0));
        }
        //-----------------------------------------Finish processing parameters-------------------------------//

        //create a watermark object using process params and payload metadata
        TextWatermark watermark = new TextWatermark(position, orientation, opacity, text, new Font(size, color, font));
        watermark.setVertical(vertical);

        //check metadata for existing watermark text
        try {
            assetNode = workflowSession.getSession().getNode(asset.getPath());
            Node metadataNode = assetNode.getNode("jcr:content/metadata");
            Value copyright = metadataNode.getProperty("dc:rights").getValue();
            if(useMetadata && copyright != null) {
                watermark.setText(copyright.getString());
            }
        } catch (RepositoryException e) {
            log.debug("asset [{}] does not have metadata field dc:rights", asset.getPath());
        }

        // create a new version
        try {
            if (assetNode.isLocked()) {
                log.warn("Version can't be created for the asset ["
                    + asset.getPath() + "] as the asset is locked");
            } else {
                getAssetManager(workflowSession.getSession()).createRevision(
                    asset, "before_watermarking", "");
            }
        } catch (Exception e) {
            log.debug("error creating version for asset [{}]", asset.getPath());
            throw new WatermarkingException(e);
        }

        log.info("applying text water mark");
        //Apply a text watermark on original
        WatermarkContext ctx = new WatermarkContext(layer, watermark);
        WatermarkUtil.applyWatermark(ctx);

        final String mimeType = asset.getMimeType();
        String renditionName = "original";
        if(archive) {
            //Save watermarked image as a new rendition
            renditionName = WATERMARKED_RENDITION_NAME + mimeTypeService.getExtension(mimeType);
            log.info("saving watermarked image for asset [{}] as rendition [{}]", asset.getPath(), renditionName);
        }
        else {
            //Overwrite original with watermarked image
            log.info("saving watermarked image in original node [{}]", asset.getOriginal().getPath());
        }

        //save watermarked rendition
        saveWatermarked(layer, asset, mimeType, renditionName);

        //update metadata - add watermarking text to String[] dc:rights
        log.info("Adding [{}] to dc:rights metadata.", text);
        try {
            Node metadataNode = null;
            metadataNode = assetNode.getNode("jcr:content/metadata");
            Value copyright = workflowSession.getSession().getValueFactory().createValue(text);
            metadataNode.setProperty("dc:rights", copyright);
            workflowSession.getSession().save();
        } catch (RepositoryException e) {
            log.debug("error while attempting to update metadata [{}]", e);
            throw new WatermarkingException(e);
        }
    }

    private void saveWatermarked(Layer layer, Asset asset, String mimeType, String renditionName) throws WatermarkingException {
        double quality = mimeType.equals("image/gif") ? 255 : 1.0;
        try {
            Node parentNode = asset.getOriginal().adaptTo(Node.class).getParent();
            ImageHelper.saveLayer(layer, mimeType, quality, parentNode, renditionName, true);
            log.info("saved watermarked image in node [{}]", asset.getRendition(renditionName).getPath());
        } catch (RepositoryException e) {
            log.debug("error while saving watermarked image for asset [{}] ", asset.getName(), e);
            throw new WatermarkingException(e);
        } catch (IOException e) {
            log.debug("error while saving watermarked image for asset [{}] ", asset.getName(), e);
            throw new WatermarkingException(e);
        }
    }

    private String[] buildArguments(MetaDataMap metaData) {

        // the 'old' way, ensures backward compatibility
        String processArgs = metaData.get(Arguments.PROCESS_ARGS.name(), String.class);
        if (processArgs != null && !processArgs.equals("")) {
            return processArgs.split(",");
        }
        // the 'new' way
        else {
            List arguments = new ArrayList();

            for(Arguments arg : Arguments.values()) {
                String argName = arg.getArgumentName();
                String argValue = metaData.get(argName, String.class);
                if (StringUtils.isNotBlank(argValue)) {
                    StringBuilder builder = new StringBuilder(arg.getArgumentPrefix()).append(argValue);
                    arguments.add(builder.toString());
                }
            }

            return arguments.toArray(new String[arguments.size()]);
        }
    }

    // get thumbnail, to fix for pdf/non-image or image formats not
    private Rendition getThumbnail(Asset asset) {
        Rendition rendition = asset.getRendition(DamUtil.getThumbnailName(319, 319));
        if (rendition == null) {
            return asset.getOriginal();
        } else {
            return rendition;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy