RenditionMakerImpl.java 14.1 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.adobe.cq.gfx.Gfx
 *  com.adobe.cq.gfx.Instructions
 *  com.adobe.cq.gfx.Layer
 *  com.adobe.cq.gfx.Plan
 *  com.adobe.xmp.XMPMeta
 *  com.day.cq.dam.api.Asset
 *  com.day.cq.dam.api.Rendition
 *  com.day.cq.dam.api.handler.xmp.XMPHandler
 *  com.day.cq.dam.api.handler.xmp.XMPWriteBackOptions
 *  com.day.cq.dam.api.renditions.RenditionMaker
 *  com.day.cq.dam.api.renditions.RenditionTemplate
 *  com.day.cq.dam.commons.metadata.SimpleXmpToJcrMetadataBuilder
 *  com.day.cq.dam.commons.util.DamUtil
 *  javax.jcr.Node
 *  javax.jcr.RepositoryException
 *  javax.jcr.Session
 *  org.apache.commons.io.IOUtils
 *  org.apache.commons.lang.ArrayUtils
 *  org.apache.commons.lang.StringUtils
 *  org.apache.felix.scr.annotations.Activate
 *  org.apache.felix.scr.annotations.Component
 *  org.apache.felix.scr.annotations.Property
 *  org.apache.felix.scr.annotations.Reference
 *  org.apache.felix.scr.annotations.Service
 *  org.apache.sling.api.resource.Resource
 *  org.apache.sling.api.resource.ResourceMetadata
 *  org.apache.sling.api.resource.ResourceResolver
 *  org.apache.sling.commons.mime.MimeTypeService
 *  org.apache.sling.commons.osgi.PropertiesUtil
 *  org.osgi.service.component.ComponentContext
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.day.cq.dam.core.impl;

import com.adobe.cq.gfx.Gfx;
import com.adobe.cq.gfx.Instructions;
import com.adobe.cq.gfx.Layer;
import com.adobe.cq.gfx.Plan;
import com.adobe.xmp.XMPMeta;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.Rendition;
import com.day.cq.dam.api.handler.xmp.XMPHandler;
import com.day.cq.dam.api.handler.xmp.XMPWriteBackOptions;
import com.day.cq.dam.api.renditions.RenditionMaker;
import com.day.cq.dam.api.renditions.RenditionTemplate;
import com.day.cq.dam.commons.metadata.SimpleXmpToJcrMetadataBuilder;
import com.day.cq.dam.commons.util.DamUtil;
import com.day.cq.dam.core.impl.handler.xmp.XMPWriteBackOptionsImpl;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Activate;
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.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceMetadata;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.mime.MimeTypeService;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(metatype=1, label="Adobe CQ DAM Rendition Maker", description="Adobe CQ DAM Rendition Maker")
@Service
public class RenditionMakerImpl
implements RenditionMaker {
    private static final Logger log = LoggerFactory.getLogger(RenditionMakerImpl.class);
    public static final String PREFIX_WEB_RENDITION = "cq5dam.web.";
    private static final boolean DEFAULT_PROPAGATE_XMP = false;
    @Property(boolValue={0}, label="Propagate XMP", description="Propagate XMP from asset metadata to renditions")
    private static final String PROPAGATE_XMP = "xmp.propagate";
    private static final String XMP_PROP_DELIM = " ";
    private static final String PS_AUX_ISO = "http://ns.adobe.com/exif/1.0/aux/ ISO";
    private static final String[] DEFAULT_XMP_EXCLUDES = new String[]{"http://ns.adobe.com/exif/1.0/aux/ ISO"};
    @Property(value={"http://ns.adobe.com/exif/1.0/aux/ ISO"}, cardinality=1, label="XMP Excludes", description="XMP properties to skip on propagation. Format: <namespace> <property>")
    private static final String XMP_EXCLUDES = "xmp.excludes";
    @Reference
    private Gfx gfx;
    @Reference
    private MimeTypeService mimeTypeService;
    @Reference
    protected XMPHandler xmpHandler;
    private boolean propagateXMP;
    private String[] xmpExcludes;

    @Activate
    private void activate(ComponentContext ctx) {
        Dictionary cfg = ctx.getProperties();
        this.propagateXMP = PropertiesUtil.toBoolean(cfg.get("xmp.propagate"), (boolean)false);
        this.xmpExcludes = PropertiesUtil.toStringArray(cfg.get("xmp.excludes"), (String[])DEFAULT_XMP_EXCLUDES);
    }

    public RenditionTemplate createThumbnailTemplate(Asset asset, int width, int height, boolean center) {
        return this.createThumbnailTemplate(asset, null, width, height, center);
    }

    public RenditionTemplate createThumbnailTemplate(Rendition rendition, int width, int height, boolean doCenter) {
        return this.createThumbnailTemplate(rendition.getAsset(), rendition, width, height, doCenter);
    }

    public RenditionTemplate createWebRenditionTemplate(Asset asset, int width, int height, int quality, String mimeType, String[] mimeTypesToKeep) {
        return this.createWebRenditionTemplate(asset, null, width, height, quality, mimeType, mimeTypesToKeep);
    }

    public RenditionTemplate createWebRenditionTemplate(Rendition rendition, int width, int height, int quality, String mimeType, String[] mimeTypesToKeep) {
        return this.createWebRenditionTemplate(rendition.getAsset(), rendition, width, height, quality, mimeType, mimeTypesToKeep);
    }

    public /* varargs */ List<Rendition> generateRenditions(Asset asset, RenditionTemplate ... templates) {
        boolean oldBatchMode = asset.isBatchMode();
        asset.setBatchMode(true);
        ArrayList<Rendition> renditions = new ArrayList<Rendition>();
        for (RenditionTemplate cfg : templates) {
            Rendition rendition = cfg.apply(asset);
            if (rendition == null) continue;
            log.info("generated rendition: {}", (Object)rendition.getPath());
            renditions.add(rendition);
        }
        if (renditions.isEmpty()) {
            asset.setBatchMode(oldBatchMode);
            return renditions;
        }
        if (this.propagateXMP) {
            this.propagateXMP(asset, renditions);
        }
        asset.setBatchMode(oldBatchMode);
        try {
            ((Node)asset.adaptTo(Node.class)).getSession().save();
        }
        catch (RepositoryException e) {
            log.error("Error while generating renditions for asset " + asset.getPath(), (Throwable)e);
        }
        return renditions;
    }

    private RenditionTemplate createThumbnailTemplate(Asset asset, Rendition rendition, int width, int height, boolean doCenter) {
        String[] arrstring;
        PlanBasedTemplate template = new PlanBasedTemplate();
        if (doCenter) {
            String[] arrstring2 = new String[1];
            arrstring = arrstring2;
            arrstring2[0] = "margin";
        } else {
            arrstring = null;
        }
        template.renditionName = DamUtil.getThumbnailName((int)width, (int)height, (String[])arrstring);
        template.mimeType = "image/png";
        template.plan = this.gfx.createPlan();
        template.plan.layer(0).set("src", (Object)(rendition != null ? rendition.getPath() : asset.getPath()));
        RenditionMakerImpl.applyThumbnail(width, height, doCenter, template.mimeType, template.plan);
        return template;
    }

    private RenditionTemplate createWebRenditionTemplate(Asset asset, Rendition rendition, int width, int height, int quality, String mimeType, String[] mimeTypesToKeep) {
        PlanBasedTemplate template = new PlanBasedTemplate();
        String assetMimeType = asset.getMimeType();
        template.mimeType = ArrayUtils.contains((Object[])mimeTypesToKeep, (Object)assetMimeType) ? assetMimeType : mimeType;
        template.renditionName = "cq5dam.web." + width + "." + height + "." + this.mimeTypeService.getExtension(template.mimeType);
        template.plan = this.gfx.createPlan();
        template.plan.layer(0).set("src", (Object)(rendition != null ? rendition.getPath() : asset.getPath()));
        this.applyWebRendition(width, height, quality, template.mimeType, template.plan);
        return template;
    }

    private static void applyThumbnail(int width, int height, boolean doCenter, String mimeType, Plan plan) {
        Instructions global = plan.view();
        global.set("wid", (Object)width);
        global.set("hei", (Object)height);
        global.set("fit", (Object)(doCenter ? "fit,0" : "constrain,0"));
        String fmt = StringUtils.substringAfter((String)mimeType, (String)"/");
        if ("png".equals(fmt) || "gif".equals(fmt) || "tif".equals(fmt)) {
            fmt = fmt + "-alpha";
        }
        global.set("fmt", (Object)fmt);
    }

    private void applyWebRendition(int width, int height, int quality, String mimeType, Plan plan) {
        Instructions global = plan.view();
        global.set("wid", (Object)width);
        global.set("hei", (Object)height);
        global.set("fit", (Object)"constrain,0");
        if ("image/jpg".equals(mimeType) || "image/jpeg".equals(mimeType)) {
            global.set("qlt", (Object)quality);
        } else if ("image/gif".equals(mimeType)) {
            global.set("quantize", (Object)("adaptive,diffuse," + quality));
        }
        String fmt = StringUtils.substringAfter((String)mimeType, (String)"/");
        if ("png".equals(fmt) || "gif".equals(fmt) || "tif".equals(fmt)) {
            fmt = fmt + "-alpha";
        }
        global.set("fmt", (Object)fmt);
    }

    private static boolean needsUpdate(Asset asset, String renditionName) {
        Rendition rendition = asset.getRendition(renditionName);
        if (rendition == null) {
            return true;
        }
        long originalLastMod = asset.getOriginal().getResourceMetadata().getModificationTime();
        if (originalLastMod < 0) {
            return true;
        }
        long renditionLastMod = rendition.getResourceMetadata().getModificationTime();
        return renditionLastMod < originalLastMod;
    }

    private void propagateXMP(Asset asset, List<Rendition> renditions) {
        if (this.xmpHandler != null && this.xmpHandler.isSupported(asset.getMimeType())) {
            XMPWriteBackOptionsImpl writeBackOptions = new XMPWriteBackOptionsImpl();
            writeBackOptions.createVersion(false);
            writeBackOptions.setRenditions(new HashSet<Rendition>(renditions));
            this.writeXmp(asset, writeBackOptions, this.xmpHandler);
        }
    }

    private synchronized void writeXmp(Asset asset, XMPWriteBackOptions writeBackOptions, XMPHandler xmpHandler) {
        try {
            Node assetNode = (Node)asset.adaptTo(Node.class);
            Node metadataNode = assetNode.getNode("jcr:content/metadata");
            SimpleXmpToJcrMetadataBuilder metadataAdapter = new SimpleXmpToJcrMetadataBuilder();
            XMPMeta xmpMeta = metadataAdapter.getXmpFromJcr(metadataNode);
            for (String exclude : this.xmpExcludes) {
                String[] split = exclude.split(" ", 2);
                if (split.length == 2) {
                    String ns = split[0];
                    String prop = split[1];
                    if (!xmpMeta.doesPropertyExist(ns, prop)) continue;
                    log.debug("Excluding XMP property: {} {}", (Object)ns, (Object)prop);
                    xmpMeta.deleteProperty(ns, prop);
                    continue;
                }
                log.warn("Configuration xmp.excludes has entry that does not have the format '<namespace> <property>': {}", (Object)exclude);
            }
            xmpHandler.writeXmp(asset, xmpMeta, writeBackOptions);
        }
        catch (Throwable e) {
            if (log.isDebugEnabled()) {
                log.debug("Could not propagate xmp to renditions of asset: '" + asset.getPath() + "'", e);
            }
            log.warn("Could not propagate xmp to renditions of asset '{}': {}", (Object)asset.getPath(), (Object)e.getMessage());
        }
    }

    protected void bindGfx(Gfx gfx) {
        this.gfx = gfx;
    }

    protected void unbindGfx(Gfx gfx) {
        if (this.gfx == gfx) {
            this.gfx = null;
        }
    }

    protected void bindMimeTypeService(MimeTypeService mimeTypeService) {
        this.mimeTypeService = mimeTypeService;
    }

    protected void unbindMimeTypeService(MimeTypeService mimeTypeService) {
        if (this.mimeTypeService == mimeTypeService) {
            this.mimeTypeService = null;
        }
    }

    protected void bindXmpHandler(XMPHandler xMPHandler) {
        this.xmpHandler = xMPHandler;
    }

    protected void unbindXmpHandler(XMPHandler xMPHandler) {
        if (this.xmpHandler == xMPHandler) {
            this.xmpHandler = null;
        }
    }

    private class PlanBasedTemplate
    implements RenditionTemplate {
        public Plan plan;
        public String renditionName;
        public String mimeType;

        private PlanBasedTemplate() {
        }

        /*
         * WARNING - Removed try catching itself - possible behaviour change.
         */
        public Rendition apply(Asset asset) {
            if (RenditionMakerImpl.needsUpdate(asset, this.renditionName)) {
                InputStream stream = null;
                try {
                    stream = RenditionMakerImpl.this.gfx.render(this.plan, ((Resource)asset.adaptTo(Resource.class)).getResourceResolver());
                    if (stream == null) {
                        log.info("cannot generate rendition. stream not found  [{}]", (Object)this.renditionName);
                        Rendition rendition = null;
                        return rendition;
                    }
                    Rendition rendition = asset.addRendition(this.renditionName, stream, this.mimeType);
                    return rendition;
                }
                finally {
                    IOUtils.closeQuietly((InputStream)stream);
                }
            }
            log.info("skipping regeneration of rendition '{}' because original is older: {}", (Object)this.renditionName, (Object)asset.getPath());
            return null;
        }
    }

}