AEM Cloud Service - Unique assets check while uploading files to AEM in browser (avoid duplicate file names)


AEM Cloud Service Version : 2021.1.4738.20210107T143101Z-201217 (Jan 7, 2021)

Goal

Adding the following extension would restrict users from uploading duplicates files (files with same name). The check is done repository wide /content/dam 

For a server side validation, check this post

Demo | Package Install | Github



Solution

1) Create a service user configuration in repo init script ui.config\src\main\content\jcr_root\apps\eaem-cs-upload-unique-assets\osgiconfig\config.author\org.apache.sling.jcr.repoinit.RepositoryInitializer-eaem.config this is for querying and returning duplicates as JSON (in next steps). 

scripts=[
        "
        create service user eaem-service-user with path system/cq:services/experience-aem
        set principal ACL for eaem-service-user
                allow jcr:read on /apps
                allow jcr:read on /content/dam
        end
        "
]

2) Adding the above repo init script in code repo and installing on AEM CS creates a service user in /home/users/system/cq:services/experience-aem


3) Add the service user to bundle mapping in ui.config\src\main\content\jcr_root\apps\eaem-cs-upload-unique-assets\osgiconfig\config.author\org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-eaem.xml

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root
        xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
        xmlns:jcr="http://www.jcp.org/jcr/1.0"
        jcr:primaryType="sling:OsgiConfig"
        user.mapping="[eaem-cs-upload-unique-assets.core:eaem-service-user=[eaem-service-user]]"/>


4) Create a servlet apps.experienceaem.assets.core.servlets.FindAssetDuplicates to return the duplicates 

package apps.experienceaem.assets.core.servlets;

import com.day.cq.search.PredicateGroup;
import com.day.cq.search.Query;
import com.day.cq.search.QueryBuilder;
import com.day.cq.search.result.Hit;
import com.day.cq.search.result.SearchResult;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.commons.json.JSONArray;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Session;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component(
        name = "Experience AEM find duplicates servlet",
        immediate = true,
        service = Servlet.class,
        property = {
                "sling.servlet.methods=GET",
                "sling.servlet.paths=/bin/experience-aem/duplicates"
        }
)
public class FindAssetDuplicates extends SlingAllMethodsServlet {
    private static final Logger log = LoggerFactory.getLogger(FindAssetDuplicates.class);

    private static final String EAEM_SERVICE_USER = "eaem-service-user";

    @Reference
    private ResourceResolverFactory factory;

    @Reference
    private QueryBuilder builder;

    @Override
    protected final void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws
            ServletException, IOException {
        try {
            String fileNames[] = request.getParameterValues("fileName");
            JSONArray duplicates = new JSONArray();

            response.setContentType("application/json");

            if(ArrayUtils.isEmpty(fileNames)){
                duplicates.write(response.getWriter());
                return;
            }

            ResourceResolver resourceResolver = getServiceResourceResolver(factory);

            Query query = builder.createQuery(PredicateGroup.create(getQueryPredicateMap(fileNames)),
                                        resourceResolver.adaptTo(Session.class));

            SearchResult result = query.getResult();

            for (Hit hit : result.getHits()) {
                duplicates.put(hit.getPath());
            }

            duplicates.write(response.getWriter());
        } catch (Exception e) {
            log.error("Could not execute duplicates check", e);
            response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    public ResourceResolver getServiceResourceResolver(ResourceResolverFactory resourceResolverFactory) {
        Map<String, Object> subServiceUser = new HashMap<>();
        subServiceUser.put(ResourceResolverFactory.SUBSERVICE, EAEM_SERVICE_USER);
        try {
            return resourceResolverFactory.getServiceResourceResolver(subServiceUser);
        } catch (LoginException ex) {
            log.error("Could not login as SubService user {}, exiting SearchService service.", "eaem-service-user", ex);
return null; } } private Map<String, String> getQueryPredicateMap(String[] fileNames) { Map<String, String> map = new HashMap<>(); map.put("path", "/content/dam"); map.put("group.p.or", "true"); for(int index = 0; index < fileNames.length; index++){ map.put("group." + index + "_nodename", fileNames[index]); } return map; } }


5) Create a client library /apps/eaem-cs-upload-unique-assets/clientlibs/unique-names with categories =dam.gui.coral.fileupload and dependencies=eaem.lodash and following script

(function ($, $document) {
    "use strict";

    var _ = window._;

    var _origConfirmUpload = window.DamFileUpload.prototype._confirmUpload,
        _origOnInputChange = window.Dam.ChunkFileUpload.prototype._onInputChange;

    window.Dam.ChunkFileUpload.prototype._onInputChange = function(event){
        var files = event.target.files;

        if(!files && event.dataTransfer && event.dataTransfer.files){
            files = event.dataTransfer.files;
        }

        if(_.isEmpty(files)){
            _origOnInputChange.call(this, event);
            return;
        }

        var validFiles = [];

        _.each(files, function(file) {
            validFiles.push(file.name);
        });

        var existInDAMFiles = checkFilesExist(validFiles);

        if(!_.isEmpty(existInDAMFiles)){
            showAlert("Following files exist : BR BR" + existInDAMFiles.join("BR") + "</b>");
            return;
        }

        _origOnInputChange.call(this, event);
    };

    window.DamFileUpload.prototype._confirmUpload = function (event) {
        var existInDAMFiles = checkFilesExist(this.fileUpload.uploadQueue.map(hit => hit.path));

        if(!_.isEmpty(existInDAMFiles)){
            showAlert("Following files exist : BR BR" + existInDAMFiles.join("BR") + "</b>");
            return;
        }

        _origConfirmUpload.call(this, event);

        var uploadDialog = this.uploadDialog;

        _.defer(function(){
            $(uploadDialog).find("input").attr("disabled", "disabled");
        },0)
    };

    function checkFilesExist(fileNames){
        var existingFiles = [],
            url = "/bin/experience-aem/duplicates?";

        _.each(fileNames, function(fileName, index){
            url = url + "fileName=" + fileName + "&";
        });

        $.ajax( { url : url, async : false, contentType:"application/json" }).done(function(data){
            existingFiles = data;
        }).fail(function() {
            showAlert("Error occured while checking for duplicates", "Error");
        });

        return existingFiles;
    }

    function showAlert(message, title, callback){
        var fui = $(window).adaptTo("foundation-ui"),
            options = [{
                id: "ok",
                text: "OK",
                primary: true
            }];

        message = message || "Unknown Error";
        title = title || "Error";

        fui.prompt(title, message, "warning", options, callback);
    }
}(jQuery, jQuery(document)));


No comments:

Post a Comment