Adobe-Consulting-Services/acs-aem-commons

View on GitHub
bundle/src/main/java/com/adobe/acs/commons/dam/AbstractRenditionModifyingProcess.java

Summary

Maintainability
A
0 mins
Test Coverage
/*
 * ACS AEM Commons
 *
 * Copyright (C) 2013 - 2023 Adobe
 *
 * 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.
 */
package com.adobe.acs.commons.dam;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;

import javax.imageio.IIOException;

import com.adobe.acs.commons.util.WorkflowHelper;
import com.day.cq.dam.commons.util.DamUtil;
import com.day.cq.workflow.exec.WorkflowProcess;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.commons.mime.MimeTypeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.osgi.annotation.versioning.ConsumerType;

import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.Rendition;
import com.day.cq.dam.commons.util.PrefixRenditionPicker;
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;

/**
 * Abstract asset workflow which performs some action on a particular rendition
 * (which was presumably created by an earlier workflow process).
 * 
 * Arguments:
 * <ul>
 * <li>renditionName - The name of the rendition to modify.</li>
 * </ul>
 *
 */
@ConsumerType
public abstract class AbstractRenditionModifyingProcess {

    private static final int MAX_GIF_QUALITY = 255;

    private static final String DEFAULT_QUALITY = "60";

    private static final String ARG_QUALITY = "quality";

    private static final String ARG_RENDITION_NAME = "renditionName";

    /**
     * Logger instance for this class.
     */
    private static final Logger log = LoggerFactory.getLogger(AbstractRenditionModifyingProcess.class);

    public final void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metaData, WorkflowHelper workflowHelper)
            throws WorkflowException {
        String[] args = workflowHelper.buildArguments(metaData);

        final String renditionName = workflowHelper.getValuesFromArgs(ARG_RENDITION_NAME, args).size() > 0 ? workflowHelper.getValuesFromArgs(
                ARG_RENDITION_NAME, args).get(0) : null;

        // image quality: from 0 t0 100%
        final String qualityStr = workflowHelper.getValuesFromArgs(ARG_QUALITY, args).size() > 0 ? workflowHelper.getValuesFromArgs(ARG_QUALITY, args)
                .get(0) : DEFAULT_QUALITY;

        if (renditionName == null) {
            log.warn("Rendition name was not configured in arguments. Skipping.");
            return;
        }

        final WorkflowHelper.AssetResourceResolverPair pair = workflowHelper.getAssetFromPayload(workItem, workflowSession);
        if (pair == null) {
            return;
        }
        final Rendition rendition = pair.asset.getRendition(new PrefixRenditionPicker(renditionName));

        if (rendition == null) {
            log.warn("Rendition name {} was not available for asset {}. Skipping.", renditionName, pair.asset);
            return;
        }

        Layer layer = null;
        try {
            layer = new Layer(rendition.getStream());

            layer = processLayer(layer, rendition, workflowSession, args);

            String mimetype = layer.getMimeType();
            double quality = mimetype.equals("image/gif") ? workflowHelper.getQuality(MAX_GIF_QUALITY, qualityStr) : workflowHelper.getQuality(1.0,
                    qualityStr);

            saveImage(pair.asset, rendition, layer, mimetype, quality, workflowHelper);
        } catch (IIOException e) {
            log.warn("Unable to load image layer from " + rendition.getPath(), e);
        } catch (IOException e) {
            log.warn("Unable to load image layer from " + rendition.getPath(), e);
        } finally {
            if (layer != null) {
                layer.dispose();
                layer = null;
            }
            pair.resourceResolver.close();
        }

    }
    
    // False positive, file path not controlled by the user
    @SuppressWarnings("findsecbugs:PATH_TRAVERSAL_IN")
    void saveImage(Asset asset, Rendition toReplace, Layer layer, String mimetype, double quality, WorkflowHelper workflowHelper)
            throws IOException {
        File tmpFile = Files.createTempFile(getTempFileSpecifier(), "." + workflowHelper.getExtension(mimetype)).toFile();
        OutputStream out = FileUtils.openOutputStream(tmpFile);
        InputStream is = null;
        try {
            layer.write(mimetype, quality, out);
            is = FileUtils.openInputStream(tmpFile);
            asset.addRendition(toReplace.getName(), is, mimetype);
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(is);
            FileUtils.deleteQuietly(tmpFile);
        }
    }


    /**
     * Create a specifier to be used for temporary file location.
     * 
     * @return the temp file qualifier
     */
    protected abstract String getTempFileSpecifier();

    /**
     * Perform the actual layer processing and return the layer to be saved.
     * 
     * @param layer the source image data
     * @param rendition the source rendition object
     * @param workflowSession the workflow session
     * @param args the parsed process arguments
     * 
     * @return the modified layer
     */
    protected abstract Layer processLayer(Layer layer, Rendition rendition, WorkflowSession workflowSession,
            String[] args);
}