com.adobe.acs.commons.mcp.impl.processes.cfi.ContentFragmentImport.java Source code

Java tutorial

Introduction

Here is the source code for com.adobe.acs.commons.mcp.impl.processes.cfi.ContentFragmentImport.java

Source

/*
 * #%L
 * ACS AEM Commons Bundle
 * %%
 * Copyright (C) 2018 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.cfi;

import com.adobe.acs.commons.data.CompositeVariant;
import com.adobe.acs.commons.data.Spreadsheet;
import com.adobe.acs.commons.fam.ActionManager;
import com.adobe.acs.commons.fam.actions.Actions;
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.FileUploadComponent;
import com.adobe.acs.commons.mcp.form.FormField;
import com.adobe.acs.commons.mcp.model.GenericReport;
import com.adobe.cq.dam.cfm.ContentElement;
import com.adobe.cq.dam.cfm.ContentFragment;
import com.adobe.cq.dam.cfm.ContentFragmentException;
import com.adobe.cq.dam.cfm.FragmentTemplate;
import com.day.cq.commons.jcr.JcrConstants;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;

/**
 * Import a series of content fragments from a spreadsheet
 */
public class ContentFragmentImport extends ProcessDefinition {

    public enum ReportColumns {
        ITEM, ACTION, DESCRIPTION, COUNT
    }

    List<EnumMap<ReportColumns, Object>> reportRows = new ArrayList();
    EnumMap<ReportColumns, Object> createdFolders = trackActivity("All folders", "Create",
            "Count of all folders created");
    EnumMap<ReportColumns, Object> importedFragments = trackActivity("Fragments", "Import",
            "Count of all imported fragments");
    EnumMap<ReportColumns, Object> skippedFragments = trackActivity("Fragments", "Skipped",
            "Count of skipped fragments");

    public static final String PATH = "path";
    public static final String FOLDER_TITLE = "folderTitle";
    public static final String NAME = "name";
    public static final String TITLE = "title";
    public static final String TEMPLATE = "template";
    public static final String DEFAULT_FOLDER_TYPE = "sling:Folder";

    @FormField(name = "Fragment data", component = FileUploadComponent.class)
    transient RequestParameter importFile;
    transient Spreadsheet spreadsheet;

    @FormField(name = "Dry run", component = CheckboxComponent.class)
    transient boolean dryRunMode;

    @FormField(name = "Detailed Report", component = CheckboxComponent.class, options = "checked")
    transient boolean detailedReport = true;

    @Override
    public void init() throws RepositoryException {
        try {
            // Read spreadsheet
            spreadsheet = new Spreadsheet(importFile, PATH, TEMPLATE, NAME, TITLE).buildSpreadsheet();
        } catch (IOException ex) {
            throw new RepositoryException("Unable to process spreadsheet", ex);
        }
    }

    @Override
    public void buildProcess(ProcessInstance instance, ResourceResolver rr)
            throws LoginException, RepositoryException {
        instance.defineCriticalAction("Create folders", rr, this::createFolders);
        instance.defineCriticalAction("Import fragments", rr, this::importFragments);
    }

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

    // Tracker
    private synchronized EnumMap<ReportColumns, Object> trackActivity(String item, String action,
            String description) {
        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);
        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);
        } 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);
    }

    // Build folders
    protected void createFolders(ActionManager manager) throws IOException {
        manager.deferredWithResolver(r -> {
            Map<String, String> folders = new TreeMap<>();
            spreadsheet.getDataRowsAsCompositeVariants().forEach(row -> {
                String path = getString(row, PATH);
                String folderTitle = getString(row, FOLDER_TITLE);
                if (!folders.containsKey(path)) {
                    folders.put(path, folderTitle);
                    manager.deferredWithResolver(Actions.retry(10, 100, rr -> {
                        manager.setCurrentItem(path);
                        createFolderNode(path, folderTitle, rr);
                    }));
                }
            });
        });
    }

    protected boolean createFolderNode(String path, String folderTitle, ResourceResolver r)
            throws RepositoryException, PersistenceException {
        if (path == null) {
            return false;
        }
        if (dryRunMode) {
            return true;
        }
        String parentPath = StringUtils.substringBeforeLast(path, "/");
        boolean titleProvided;
        String folderName = StringUtils.substringAfterLast(path, "/");
        if (folderTitle == null) {
            folderTitle = folderName;
            titleProvided = false;
        } else {
            titleProvided = true;
        }
        Session s = r.adaptTo(Session.class);
        if (s.nodeExists(path) && titleProvided) {
            return updateFolderTitle(s, path, folderTitle, r);
        } else if (!s.nodeExists(path)) {
            if (!s.nodeExists(parentPath)) {
                createFolderNode(parentPath, null, r);
            }
            Node child = s.getNode(parentPath).addNode(folderName, DEFAULT_FOLDER_TYPE);
            trackDetailedActivity(path, "Create Folder", "Create folder", 0L);
            setFolderTitle(child, folderTitle);
            incrementCount(createdFolders, 1L);
            r.commit();
            r.refresh();
            return true;
        }
        return false;
    }

    private boolean updateFolderTitle(Session s, String path, String folderTitle, ResourceResolver r)
            throws PersistenceException, RepositoryException {
        Node folderNode = s.getNode(path);
        Node folderContentNode = folderNode.hasNode(JcrConstants.JCR_CONTENT)
                ? folderNode.getNode(JcrConstants.JCR_CONTENT)
                : null;
        if (null != folderContentNode && folderContentNode.hasProperty(JcrConstants.JCR_TITLE)
                && folderContentNode.getProperty(JcrConstants.JCR_TITLE).getString().equals(folderTitle)) {
            return false;
        } else {
            if (folderContentNode == null) {
                folderContentNode = folderNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_UNSTRUCTURED);
            }
            folderContentNode.setProperty(JcrConstants.JCR_TITLE, folderTitle);
            r.commit();
            r.refresh();
            return true;
        }
    }

    private void setFolderTitle(Node child, String title) throws RepositoryException {
        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);
        }
    }

    // Create/Update fragment models
    //    -- https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/content-fragment-templates.html
    // Create/Update fragments
    // -- https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/javadoc/com/adobe/cq/dam/cfm/ContentFragmentManager.html
    protected void importFragments(ActionManager manager) throws IOException {
        manager.deferredWithResolver(r -> {
            spreadsheet.getDataRowsAsCompositeVariants().forEach(row -> {
                manager.deferredWithResolver(rr -> {
                    importFragment(rr, row);
                });
            });
        });
    }

    private void importFragment(ResourceResolver rr, Map<String, CompositeVariant> row)
            throws ContentFragmentException, PersistenceException {
        String name = getString(row, NAME);
        String title = getString(row, TITLE);
        String path = getString(row, PATH);
        String template = getString(row, TEMPLATE);
        Resource templateResource = getFragmentTemplateResource(rr, template);
        if (templateResource == null) {
            throw new ContentFragmentException("Unable to locate template " + template);
        }

        boolean created;

        if (dryRunMode) {
            created = rr.getResource(path + "/" + name) == null;
        } else {
            ContentFragment cf = getOrCreateFragment(rr.getResource(path), templateResource, name, title);
            created = rr.hasChanges();
            setContentElements(cf, row);
            setAssetMetadata(row, cf);
            if (rr.hasChanges()) {
                incrementCount(importedFragments, 1L);
                rr.commit();
            } else {
                incrementCount(skippedFragments, 1L);
            }
        }

        if (detailedReport) {
            if (created) {
                trackDetailedActivity("Created Fragment", path, "Created fragment " + name, 0L);
            } else if (rr.hasChanges()) {
                trackDetailedActivity("Updated Fragment", path, "Updated existing fragment " + name, 0L);
            }
        }
    }

    private void setAssetMetadata(Map<String, CompositeVariant> row, ContentFragment cf)
            throws ContentFragmentException {
        for (Entry<String, CompositeVariant> col : row.entrySet()) {
            if (col.getKey().contains(":") && col.getValue() != null) {
                if (col.getValue().isArray()) {
                    cf.setMetaData(col.getKey(), col.getValue().getValues().toArray());
                } else {
                    cf.setMetaData(col.getKey(), col.getValue().getValueAs(String.class));
                }
            }
        }
    }

    private void setContentElements(ContentFragment cf, Map<String, CompositeVariant> row)
            throws ContentFragmentException {
        for (Iterator<ContentElement> i = cf.getElements(); i.hasNext();) {
            ContentElement contentElement = i.next();
            String elementName = contentElement.getName();
            String value = getString(row, elementName);
            String currentValue = contentElement.getContent();

            if (!String.valueOf(value).equals(String.valueOf(currentValue))) {
                contentElement.setContent(value, contentElement.getContentType());
            }
        }
    }

    private String getString(Map<String, CompositeVariant> row, String attr) {
        CompositeVariant v = row.get(attr.toLowerCase()); // Workaround issue #1428
        if (v != null) {
            return (String) v.getValueAs(String.class);
        } else {
            return null;
        }
    }

    protected ContentFragment getOrCreateFragment(Resource parent, Resource template, String name, String title)
            throws ContentFragmentException {
        Resource fragmentResource = parent.getChild(name);
        if (fragmentResource == null) {
            try {
                FragmentTemplate fragmentTemplate = template.adaptTo(FragmentTemplate.class);
                // TODO: Replace this reflection hack with the proper method once ACS Commons doesn't support 6.2 anymore
                return (ContentFragment) MethodUtils.invokeMethod(fragmentTemplate, "createFragment", parent, name,
                        title);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
                Logger.getLogger(ContentFragmentImport.class.getName()).log(Level.SEVERE,
                        "Unable to call createFragment method -- Is this 6.3 or newer?", ex);
                return null;
            }
        } else {
            return fragmentResource.adaptTo(ContentFragment.class);
        }
    }

    protected Resource getFragmentTemplateResource(ResourceResolver rr, String templatePath) {
        Resource template = rr.resolve(templatePath);
        if (template.adaptTo(FragmentTemplate.class) != null) {
            return template;
        } else {
            return template.getChild("jcr:content");
        }
    }
}