com.adobe.acs.commons.mcp.impl.processes.asset.AssetIngestor.java Source code

Java tutorial

Introduction

Here is the source code for com.adobe.acs.commons.mcp.impl.processes.asset.AssetIngestor.java

Source

/*
 * #%L
 * ACS AEM Commons Bundle
 * %%
 * Copyright (C) 2017 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.
 * #L%
 */
package com.adobe.acs.commons.mcp.impl.processes.asset;

import com.adobe.acs.commons.fam.ActionManager;
import com.adobe.acs.commons.functions.CheckedConsumer;
import com.adobe.acs.commons.mcp.ProcessDefinition;
import com.adobe.acs.commons.mcp.ProcessInstance;
import com.adobe.acs.commons.mcp.form.CheckboxComponent;
import com.adobe.acs.commons.mcp.form.FormField;
import com.adobe.acs.commons.mcp.form.PathfieldComponent;
import com.adobe.acs.commons.mcp.form.RadioComponent;
import com.adobe.acs.commons.mcp.model.FieldFormat;
import com.adobe.acs.commons.mcp.model.GenericReport;
import com.adobe.acs.commons.mcp.model.ValueFormat;
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.AssetManager;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.mime.MimeTypeService;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;

public abstract class AssetIngestor extends ProcessDefinition {

    public static final String ALL_ASSETS = "All Assets";
    static final String[] AUTHORIZED_GROUPS = new String[] { "administrators", "asset-ingest",
            "dam-administrators" };

    protected final transient MimeTypeService mimetypeService;

    @SuppressWarnings("squid:S00115")
    public static enum AssetAction {
        skip, version, replace
    }

    public AssetIngestor(MimeTypeService mimeTypeService) {
        this.mimetypeService = mimeTypeService;
    }

    @FormField(name = "Dry run", description = "If checked, no import happens.  Useful for data validation", component = CheckboxComponent.class)
    boolean dryRunMode = false;

    @FormField(name = "Detailed report", description = "If checked, information about every asset is recorded", component = CheckboxComponent.class, options = "checked")
    boolean detailedReport = true;

    @FormField(name = "Inhibit workflow", description = "If checked, disables asset processing workflow", component = CheckboxComponent.class, options = "checked")
    boolean inhibitWorkflow = true;

    @FormField(name = "Preserve Filename", description = "If checked, file name is preserved as asset name.  If unchecked, asset name is converted to a JCR-friendly name.", component = CheckboxComponent.class, options = "checked")
    boolean preserveFileName = true;

    @FormField(name = "Target JCR Folder", description = "Prepended to target path if it does not begin with /content", hint = "/content/dam", component = PathfieldComponent.FolderSelectComponent.class, required = true, options = {
            "default=/content/dam", "base=/content/dam" })
    String jcrBasePath = "/content/dam";
    @FormField(name = "Ignore folders", description = "List of folder names to be ignored", hint = "tmp,.DS_STORE", options = {
            "default=tmp,ds_store,.ds_store,.thumbs,.appledouble" })
    String ignoreFolders = "tmp,ds_store,.ds_store,.thumbs,.appledouble";
    List<String> ignoreFolderList;

    @FormField(name = "Ignore files", description = "List of file names to ignore", hint = "full file names, comma separated", options = {
            "default=ds_store,.ds_store" })
    String ignoreFiles = "ds_store,.ds_store";
    List<String> ignoreFileList;

    @FormField(name = "Ignore extensions", description = "List of file extensions to ignore", hint = "mp4,txt, etc.", options = {
            "default=txt,html,css,js,thm,exe,db" })
    String ignoreExtensions = "txt,html,css,js,thm,exe,db";
    List<String> ignoreExtensionList;

    @FormField(name = "Existing action", description = "What to do if an asset exists", component = RadioComponent.EnumerationSelector.class, options = {
            "default=skip", "vertical" })
    protected transient AssetAction existingAssetAction = AssetAction.skip;
    @FormField(name = "Minimum size", description = "Min size to import (in bytes), 0=none", hint = "1024...", options = {
            "default=1024" })
    private transient long minimumSize = 1024;
    @FormField(name = "Maximum size", description = "Max size to import (in bytes), 0=none", hint = "1gb = 1073741824", options = {
            "default=1073741824" })
    private transient long maximumSize = 1073741824;

    protected static final String DEFAULT_FOLDER_TYPE = "sling:Folder";
    protected static final String CHANGED_BY_WORKFLOW = "changedByWorkflowProcess";

    EnumMap<ReportColumns, Object> createdFolders = trackActivity("All folders", "Create",
            "Count of all folders created", 0L);
    EnumMap<ReportColumns, Object> importedAssets = trackActivity(ALL_ASSETS, "Import",
            "Count of all assets imports", 0L);
    EnumMap<ReportColumns, Object> skippedFiles = trackActivity(ALL_ASSETS, "Skipped", "Count of skipped files",
            0L);
    EnumMap<ReportColumns, Object> importedData = trackActivity(ALL_ASSETS, "Data imported",
            "Count of bytes imported", 0L);

    @SuppressWarnings("squid:S00115")
    public static enum ReportColumns {
        item, action, description, count, @FieldFormat(ValueFormat.storageSize)
        bytes
    }

    List<EnumMap<ReportColumns, Object>> reportRows;

    private synchronized EnumMap<ReportColumns, Object> trackActivity(String item, String action,
            String description, Long bytes) {
        if (reportRows == null) {
            reportRows = Collections.synchronizedList(new ArrayList<>());
        }
        EnumMap<ReportColumns, Object> reportRow = new EnumMap<>(ReportColumns.class);
        reportRow.put(ReportColumns.item, item);
        reportRow.put(ReportColumns.action, action);
        reportRow.put(ReportColumns.description, description);
        reportRow.put(ReportColumns.count, 0L);
        reportRow.put(ReportColumns.bytes, bytes);
        reportRows.add(reportRow);
        return reportRow;
    }

    protected synchronized EnumMap<ReportColumns, Object> trackDetailedActivity(String item, String action,
            String description, Long bytes) {
        if (detailedReport) {
            return trackActivity(item, action, description, bytes);
        } else {
            return null;
        }
    }

    @SuppressWarnings("squid:S2445")
    private void increment(EnumMap<ReportColumns, Object> row, ReportColumns col, long amt) {
        if (row != null) {
            synchronized (row) {
                row.put(col, (Long) row.getOrDefault(col, 0) + amt);
            }
        }
    }

    protected void incrementCount(EnumMap<ReportColumns, Object> row, long amt) {
        increment(row, ReportColumns.count, amt);
    }

    protected void incrementBytes(EnumMap<ReportColumns, Object> row, long amt) {
        increment(row, ReportColumns.bytes, amt);
    }

    protected long getCount(EnumMap<ReportColumns, Object> row) {
        return (long) row.getOrDefault(ReportColumns.count, 0);
    }

    @Override
    public void init() throws RepositoryException {
        if (ignoreFolders == null) {
            ignoreFolders = "";
        }
        ignoreFolderList = Arrays.asList(ignoreFolders.trim().toLowerCase().split(","));
        if (ignoreFiles == null) {
            ignoreFiles = "";
        }
        ignoreFileList = Arrays.asList(ignoreFiles.trim().toLowerCase().split(","));
        if (ignoreExtensions == null) {
            ignoreExtensions = "";
        }
        ignoreExtensionList = Arrays.asList(ignoreExtensions.trim().toLowerCase().split(","));
    }

    @SuppressWarnings("squid:S00112")
    private void createAsset(Source source, String assetPath, ResourceResolver r, boolean versioning)
            throws Exception {
        try {
            boolean versioned = false;
            if (!dryRunMode) {
                disableWorkflowProcessing(r);
                AssetManager assetManager = r.adaptTo(AssetManager.class);
                String type = mimetypeService.getMimeType(source.getName());
                if (versioning) {
                    //if asset is null, no version gets created
                    Asset asset = r.getResource(assetPath).adaptTo(Asset.class);
                    versioned = asset != null;
                    //once you are past this first version, default behavior is to start numbering 1.0, 1.1 and so on
                    assetManager.createRevision(asset, "initial version of asset", asset.getName());
                    r.commit();
                    r.refresh();
                    //once version is committed we are safe to create, which only replaces the original version
                }
                assetManager.createAsset(assetPath, source.getStream(), type, false);
                r.commit();
                r.refresh();
            }
            if (versioned) {
                trackDetailedActivity(assetPath, "Revised", "Created new version of asset", source.getLength());
            } else {
                trackDetailedActivity(assetPath, "Create", "Imported asset", source.getLength());
            }
            incrementBytes(importedData, source.getLength());
            incrementCount(importedAssets, 1L);
        } finally {
            source.close();
        }
    }

    protected void handleExistingAsset(Source source, String assetPath, ResourceResolver r) throws Exception {
        switch (existingAssetAction) {
        case skip:
            //if skip then we only create asset if it doesn't exist
            if (r.getResource(assetPath) == null) {
                createAsset(source, assetPath, r, false);
            } else {
                incrementCount(skippedFiles, 1L);
                trackDetailedActivity(assetPath, "Skip", "Skipped existing asset", 0L);
            }
            break;
        case replace:
            //if replace we just create a new one and the old one goes away
            createAsset(source, assetPath, r, false);
            break;
        default:
            //only option left is replace, we'll save current version as a version and then replace it
            versionExistingAsset(source, assetPath, r);
        }
    }

    @SuppressWarnings("squid:S3776")
    protected boolean createFolderNode(HierarchialElement el, ResourceResolver r)
            throws RepositoryException, PersistenceException {
        if (el == null || !el.isFolder()) {
            return false;
        }
        if (dryRunMode) {
            return true;
        }
        String folderPath = el.getNodePath();
        String name = el.getName();
        Session s = r.adaptTo(Session.class);
        if (s.nodeExists(folderPath)) {
            Node folderNode = s.getNode(folderPath);
            Node folderContentNode = folderNode.hasNode(JcrConstants.JCR_CONTENT)
                    ? folderNode.getNode(JcrConstants.JCR_CONTENT)
                    : null;
            if (folderNode.getPath().equals(jcrBasePath)
                    || (null != folderContentNode && folderContentNode.hasProperty(JcrConstants.JCR_TITLE)
                            && folderContentNode.getProperty(JcrConstants.JCR_TITLE).getString().equals(name))) {
                return false;
            } else {
                if (folderContentNode == null) {
                    folderContentNode = folderNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_UNSTRUCTURED);
                }
                folderContentNode.setProperty(JcrConstants.JCR_TITLE, name);
                r.commit();
                r.refresh();
                return true;
            }
        }
        HierarchialElement parent = el.getParent();
        String parentPath;
        if (parent == null) {
            parentPath = jcrBasePath;
        } else {
            parentPath = parent.getNodePath();
        }
        if (!jcrBasePath.equals(parentPath)) {
            createFolderNode(parent, r);
        }
        Node child = s.getNode(parentPath).addNode(el.getNodeName(), DEFAULT_FOLDER_TYPE);
        trackDetailedActivity(el.getNodePath(), "Create Folder", "Create folder", 0L);
        setFolderTitle(child, folderPath, name);
        incrementCount(createdFolders, 1L);
        r.commit();
        r.refresh();
        return true;
    }

    private void setFolderTitle(Node child, String folderPath, String title) throws RepositoryException {
        if (!folderPath.equals(jcrBasePath)) {
            if (child.hasNode(JcrConstants.JCR_CONTENT)) {
                child.getNode(JcrConstants.JCR_CONTENT).setProperty(JcrConstants.JCR_TITLE, title);
            } else {
                child.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_UNSTRUCTURED)
                        .setProperty(JcrConstants.JCR_TITLE, title);
            }
        }
    }

    @SuppressWarnings("squid:S00112")
    private void versionExistingAsset(Source source, String assetPath, ResourceResolver r) throws Exception {
        createAsset(source, assetPath, r, r.getResource(assetPath) != null);
    }

    protected CheckedConsumer<ResourceResolver> importAsset(final Source source, ActionManager actionManager) {
        return (ResourceResolver r) -> {
            createFolderNode(source.getElement().getParent(), r);
            actionManager.setCurrentItem(source.getElement().getItemName());
            HierarchialElement el = source.getElement();
            String path = source.getElement().getNodePath();
            if (null != el && el.isFile() && el.getName().contains(".") && !preserveFileName) {
                String baseName = StringUtils.substringBeforeLast(el.getName(), ".");
                String extension = StringUtils.substringAfterLast(el.getName(), ".");
                path = (el.getParent() == null ? el.getJcrBasePath() : el.getParent().getNodePath()) + "/"
                        + JcrUtil.createValidName(baseName, JcrUtil.HYPHEN_LABEL_CHAR_MAPPING, "-") + "."
                        + JcrUtil.createValidName(extension, JcrUtil.HYPHEN_LABEL_CHAR_MAPPING, "-");
            }
            handleExistingAsset(source, path, r);
        };
    }

    protected boolean canImportFile(Source source) throws IOException {
        String name = source.getName().toLowerCase();
        if (minimumSize > 0 && source.getLength() < minimumSize) {
            return false;
        }
        if (maximumSize > 0 && source.getLength() > maximumSize) {
            return false;
        }
        if (name.startsWith(".") || ignoreFileList.contains(name)) {
            return false;
        }
        if (name.contains(".")) {
            int extPos = name.lastIndexOf('.');
            String ext = name.substring(extPos + 1);
            if (ignoreExtensionList.contains(ext)) {
                return false;
            }
        }
        return true;
    }

    protected boolean canImportFolder(HierarchialElement element) {
        String name = element.getName();
        if (ignoreFolderList.contains(name.toLowerCase())) {
            return false;
        } else {
            HierarchialElement parent = element.getParent();
            if (parent == null) {
                return true;
            } else {
                return canImportFolder(parent);
            }
        }
    }

    protected boolean canImportContainingFolder(HierarchialElement element) {
        HierarchialElement parent = element.getParent();
        if (parent == null) {
            return true;
        } else {
            return canImportFolder(parent);
        }
    }

    protected void disableWorkflowProcessing(ResourceResolver rr) throws RepositoryException {
        if (inhibitWorkflow) {
            rr.adaptTo(Session.class).getWorkspace().getObservationManager().setUserData(CHANGED_BY_WORKFLOW);
        }
    }

    private transient GenericReport report = new GenericReport();

    @Override
    public void storeReport(ProcessInstance instance, ResourceResolver rr)
            throws RepositoryException, PersistenceException {
        report.setRows(reportRows, ReportColumns.class);
        report.persist(rr, instance.getPath() + "/jcr:content/report");
    }

    protected interface Source {

        String getName();

        InputStream getStream() throws IOException;

        long getLength() throws IOException;

        HierarchialElement getElement();

        void close() throws IOException;

    }

    protected interface HierarchialElement {

        boolean isFile();

        boolean isFolder();

        HierarchialElement getParent();

        String getName();

        String getItemName();

        Source getSource();

        String getJcrBasePath();

        default String getNodePath() {
            HierarchialElement parent = getParent();
            return (parent == null ? getJcrBasePath() : parent.getNodePath()) + "/" + getNodeName();
        }

        default String getNodeName() {
            String name = getName();
            if (isFile() && name.contains(".")) {
                return name;
            } else {
                return JcrUtil.createValidName(name, JcrUtil.HYPHEN_LABEL_CHAR_MAPPING, "-");
            }
        }
    }
}