com.adobe.acs.commons.mcp.impl.processes.AssetFolderCreator.java Source code

Java tutorial

Introduction

Here is the source code for com.adobe.acs.commons.mcp.impl.processes.AssetFolderCreator.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;

import com.adobe.acs.commons.fam.ActionManager;
import com.adobe.acs.commons.mcp.ProcessDefinition;
import com.adobe.acs.commons.mcp.ProcessInstance;
import com.adobe.acs.commons.mcp.form.FileUploadComponent;
import com.adobe.acs.commons.mcp.form.FormField;
import com.adobe.acs.commons.mcp.form.SelectComponent;
import com.adobe.acs.commons.mcp.model.GenericReport;
import com.adobe.acs.commons.mcp.util.StringUtil;
import com.adobe.acs.commons.util.datadefinitions.ResourceDefinition;
import com.adobe.acs.commons.util.datadefinitions.ResourceDefinitionBuilder;
import com.adobe.acs.commons.util.datadefinitions.impl.BasicResourceDefinition;
import com.day.cq.dam.api.DamConstants;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.JcrConstants;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.jcr.resource.JcrResourceConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.RepositoryException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Creates Asset Folder definitions (node and Title) based on a well defined Excel document.
 */
public class AssetFolderCreator extends ProcessDefinition implements Serializable {
    private static final Logger log = LoggerFactory.getLogger(AssetFolderCreator.class);
    private static final long serialVersionUID = 4393712954263547160L;

    public static final String NAME = "Asset Folder Creator";

    protected transient Map<String, ResourceDefinitionBuilder> resourceDefinitionBuilders;

    public enum AssetFolderBuilder {
        TITLE_TO_NODE_NAME, TITLE_AND_NODE_NAME, LOWERCASE_WITH_DASHES, NONE
    }

    enum FolderType {
        UNORDERED_FOLDER, ORDERED_FOLDER
    }

    public AssetFolderCreator(Map<String, ResourceDefinitionBuilder> resourceDefinitionBuilders) {
        this.resourceDefinitionBuilders = resourceDefinitionBuilders;
    }

    @FormField(name = "Excel File", description = "Provide the .xlsx file that defines the Asset Folder taxonomy", component = FileUploadComponent.class, options = {
            "mimeTypes=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "required" })
    public transient InputStream excelFile = null;

    @FormField(name = "Folder Type", description = "", component = SelectComponent.EnumerationSelector.class, options = {
            "default=UNORDERED_FOLDER", "required" })
    public FolderType assetFolderType = FolderType.UNORDERED_FOLDER;

    @FormField(name = "Primary Creator", description = "This will be used first and the Fallback will only be used if this fails to generate a valid Asset Folder definition.", component = SelectComponent.EnumerationSelector.class, options = {
            "default=TITLE_AND_NODE_NAME", "required" })
    public AssetFolderBuilder primary = AssetFolderBuilder.TITLE_AND_NODE_NAME;

    @FormField(name = "Fallback Creator", description = "This is only invoked when the Primary cannot generate a valid Asset Folder definition. If this can also not generate a valid Asset Folder definition then the row will be skipped.", component = SelectComponent.EnumerationSelector.class, options = {
            "default=LOWERCASE_WITH_DASHES", "required" })
    public AssetFolderBuilder fallback = AssetFolderBuilder.LOWERCASE_WITH_DASHES;

    @Override
    public void init() throws RepositoryException {
        // Initialization not required for this process
    }

    @Override
    public void buildProcess(ProcessInstance instance, ResourceResolver rr)
            throws LoginException, RepositoryException {
        report.setName(instance.getName());
        instance.getInfo().setDescription(String.format("Create Asset Folders using [ %s / %s ]",
                StringUtil.getFriendlyName(primary.name()), StringUtil.getFriendlyName(fallback.name())));

        instance.defineCriticalAction("Parse Asset Folder definitions", rr, this::parseAssetFolderDefinitions);
        instance.defineCriticalAction("Create Asset Folders", rr, this::createAssetFolders);
    }

    volatile HashMap<String, AssetFolderDefinition> assetFolderDefinitions = new LinkedHashMap<>();

    /**
     * Parses the input Excel file and creates a list of AssetFolderDefinition objects to process.
     *
     * @param manager the action manager
     * @throws IOException
     */
    public void parseAssetFolderDefinitions(ActionManager manager) throws Exception {
        manager.withResolver(rr -> {
            final XSSFWorkbook workbook = new XSSFWorkbook(excelFile);
            // Close the InputStream to prevent resource leaks.
            excelFile.close();

            final XSSFSheet sheet = workbook.getSheetAt(0);
            final Iterator<Row> rows = sheet.rowIterator();

            while (rows.hasNext()) {
                parseAssetFolderRow(rows.next());
            }
            log.info("Finished Parsing and collected [ {} ] asset folders for creation.",
                    assetFolderDefinitions.size());
        });
    }

    /**
     * Parse a row in the Excel that represents an asset folder and ancestors.
     *
     * @param row the row to process from the Excel sheet.
     */
    private void parseAssetFolderRow(final Row row) {
        final Iterator<Cell> cells = row.cellIterator();

        // The previousAssetFolderPath is reset on each new row.
        String previousAssetFolderPath = null;

        while (cells.hasNext()) {
            try {
                previousAssetFolderPath = parseAssetFolderCell(cells.next(), previousAssetFolderPath);
            } catch (IllegalArgumentException e) {
                // Error logged in throwing method parseAssetFolderCell.
                // Skip rest of row to avoid creating undesired structures with bad data.
                break;
            }
        }
    }

    /**
     * Parse a single cell from an Excel row.
     *
     * @param cell the cell to process from the Excel row.
     * @param previousAssetFolderPath the node path of the previous
     * @return the asset folder path to the asset folder represented by {@param cell}
     * @throws IllegalArgumentException
     */
    private String parseAssetFolderCell(final Cell cell, final String previousAssetFolderPath)
            throws IllegalArgumentException {
        final String cellValue = StringUtils.trimToNull(cell.getStringCellValue());

        if (StringUtils.isNotBlank(cellValue)) {

            // Generate a asset folder definition that will in turn be used to drive the asset folder definition creation
            AssetFolderDefinition assetFolderDefinition = getAssetFolderDefinition(primary, cellValue,
                    previousAssetFolderPath);

            // Try using the fallback converter if the primary convert could not resolve to a valid definition.
            if (assetFolderDefinition == null) {
                assetFolderDefinition = getAssetFolderDefinition(fallback, cellValue, previousAssetFolderPath);
            }

            if (assetFolderDefinition == null) {
                log.warn("Could not find a Asset Folder Converter that accepts value [ {} ]; skipping...",
                        cellValue);
                // Record parse failure
                record(ReportRowStatus.FAILED_TO_PARSE, "", cellValue);
                throw new IllegalArgumentException(String.format(
                        "Unable to parse value [ %s ]. Skipping rest of row to prevent undesired structured from being created.",
                        cellValue));
            } else {
                /* Prepare for next Cell */
                if (assetFolderDefinitions.get(assetFolderDefinition.getId()) == null) {
                    assetFolderDefinitions.put(assetFolderDefinition.getId(), assetFolderDefinition);
                }

                return assetFolderDefinition.getPath();
            }
        } else {
            // If cell is blank then treat as it it is empty.
            return previousAssetFolderPath;
        }
    }

    public void createAssetFolders(ActionManager manager) {
        assetFolderDefinitions.values().stream().forEach(assetFolderDefinition -> {
            try {
                manager.withResolver(rr -> {
                    createAssetFolder(assetFolderDefinition, rr);
                });
            } catch (Exception e) {
                log.error("Unable to import asset folders via ACS Commons MCP - Asset Folder Creator", e);
            }
        });
    }

    /**
     * Creates an Asset Folder.
     *
     * @param assetFolderDefinition the asset folder definition to create.
     * @param resourceResolver the resource resolver object used to create the asset folder.
     * @throws PersistenceException
     * @throws RepositoryException
     */
    protected void createAssetFolder(final AssetFolderDefinition assetFolderDefinition,
            final ResourceResolver resourceResolver) {
        ReportRowStatus status;

        Resource folder = resourceResolver.getResource(assetFolderDefinition.getPath());

        try {
            if (folder == null) {
                final Map<String, Object> folderProperties = new HashMap<>();
                folderProperties.put(JcrConstants.JCR_PRIMARYTYPE, assetFolderDefinition.getNodeType());
                folder = resourceResolver.create(
                        resourceResolver.getResource(assetFolderDefinition.getParentPath()),
                        assetFolderDefinition.getName(), folderProperties);

                status = ReportRowStatus.CREATED;
            } else {
                status = ReportRowStatus.UPDATED_FOLDER_TITLES;
            }

            final Resource jcrContent = folder.getChild(JcrConstants.JCR_CONTENT);

            if (jcrContent == null) {
                final Map<String, Object> jcrContentProperties = new HashMap<>();
                jcrContentProperties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
                resourceResolver.create(folder, JcrConstants.JCR_CONTENT, jcrContentProperties);
            }

            setTitles(folder, assetFolderDefinition);
            record(status, assetFolderDefinition.getPath(), assetFolderDefinition.getTitle());

            log.debug("Created Asset Folder [ {} -> {} ]", assetFolderDefinition.getPath(),
                    assetFolderDefinition.getTitle());
        } catch (Exception e) {
            record(ReportRowStatus.FAILED_TO_CREATE, assetFolderDefinition.getPath(),
                    assetFolderDefinition.getTitle());
            log.error("Unable to create Asset Folder [ {} -> {} ]",
                    new String[] { assetFolderDefinition.getPath(), assetFolderDefinition.getTitle() }, e);
        }
    }

    /**
     * Generates the Asset Folder Definition.
     *
     * @param assetFolderBuilder The asset folder builder to use to construct the AssetFolderDefinition.
     * @param value The value to convert into a AssetFolderDefinition.
     * @param previousAssetFolderPath The previous Asset Folder processed
     * @return a valid AssetFolderDefinition, or null if a valid AssetFolderDefinition cannot be generated.
     */
    private AssetFolderDefinition getAssetFolderDefinition(final AssetFolderBuilder assetFolderBuilder,
            final String value, String previousAssetFolderPath) {
        final ResourceDefinitionBuilder resourceDefinitionBuilder = resourceDefinitionBuilders
                .get(assetFolderBuilder.name());

        if (resourceDefinitionBuilder != null && resourceDefinitionBuilder.accepts(value)) {
            return new AssetFolderDefinition(resourceDefinitionBuilder.convert(value), previousAssetFolderPath,
                    assetFolderType);
        }

        return null;
    }

    private void setTitles(final Resource folder, final AssetFolderDefinition assetFolderDefinition)
            throws RepositoryException {
        if (folder == null) {
            log.error("Asset Folder resource [ {} ] is null", assetFolderDefinition.getPath());
            return;
        }

        Resource jcrContent = folder.getChild(JcrConstants.JCR_CONTENT);
        if (jcrContent == null) {
            log.error("Asset Folder [ {} ] does not have a jcr:content child", assetFolderDefinition.getPath());
            return;
        }

        final ModifiableValueMap properties = jcrContent.adaptTo(ModifiableValueMap.class);

        if (!StringUtils.equals(assetFolderDefinition.getTitle(),
                properties.get(com.day.cq.commons.jcr.JcrConstants.JCR_TITLE, String.class))) {
            // Ensure if the asset folder definition already exists that the title is set properly
            properties.put(com.day.cq.commons.jcr.JcrConstants.JCR_TITLE, assetFolderDefinition.getTitle());
        }
    }

    /** Reporting **/

    private final transient GenericReport report = new GenericReport();

    private final ArrayList<EnumMap<ReportColumns, Object>> reportRows = new ArrayList<>();

    private enum ReportColumns {
        STATUS, ASSET_FOLDER_PATH, ASSET_FOLDER_TITLE
    }

    public enum ReportRowStatus {
        CREATED, UPDATED_FOLDER_TITLES, FAILED_TO_PARSE, FAILED_TO_CREATE,
    }

    private void record(ReportRowStatus status, String path, String title) {
        final EnumMap<ReportColumns, Object> row = new EnumMap<>(ReportColumns.class);

        row.put(ReportColumns.STATUS, StringUtil.getFriendlyName(status.name()));
        row.put(ReportColumns.ASSET_FOLDER_PATH, path);
        row.put(ReportColumns.ASSET_FOLDER_TITLE, title);

        reportRows.add(row);
    }

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

    /** Asset Folder Definition Class **/

    protected static final class AssetFolderDefinition extends BasicResourceDefinition {
        private static final String ASSET_ROOT_PATH = DamConstants.MOUNTPOINT_ASSETS;
        private String parentPath = null;
        private FolderType folderType;

        public AssetFolderDefinition(ResourceDefinition resourceDefinition, String parentPath,
                FolderType folderType) {
            super(resourceDefinition.getName());
            super.setTitle(resourceDefinition.getTitle());
            this.folderType = folderType;
            this.parentPath = StringUtils.defaultIfBlank(parentPath, ASSET_ROOT_PATH);
            super.setPath(this.parentPath + "/" + resourceDefinition.getName());
        }

        public String getId() {
            // The Id of the Asset Folder Definition IS the path of the asset folder to create
            return getPath();
        }

        public String getParentPath() {
            return this.parentPath;
        }

        public String getNodeType() {
            if (FolderType.ORDERED_FOLDER.equals(folderType)) {
                return JcrResourceConstants.NT_SLING_ORDERED_FOLDER;
            } else {
                return JcrResourceConstants.NT_SLING_FOLDER;
            }
        }
    }
}