Goal
AEM Cloud Version : 2021.2.4944.20210221T230729Z-210225 (Feb 21, 2021)
The client side duplicate check post provides a helpful error message when user tries to upload duplicate files using a browser, however it does not protect when user uploads files using other means, say AEM Desktop app. This post provides a server side way of handling duplicate file names
Demo | Package Install | Github
Error uploading duplicate in Browser
Solution
1) Add a service user eaem-service-user in repo init script ui.config\src\main\content\jcr_root\apps\eaem-cs-server-duplicate-check\osgiconfig\config.author\org.apache.sling.jcr.repoinit.RepositoryInitializer-eaem.config
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 /conf allow jcr:read on /content/dam end " ]
2) Provide the service user to bundle mapping in ui.config\src\main\content\jcr_root\apps\eaem-cs-server-duplicate-check\osgiconfig\config.author\org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-ea.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-server-duplicate-check.core:eaem-service-user=[eaem-service-user]]"/>
3) Add a filter apps.experienceaem.assets.core.filters.DuplicateAssetNameCheck executing for .initiateUpload.json and .createasset.html requests to check for duplicate file names across the repo
package apps.experienceaem.assets.core.filters; 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.collections.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.request.RequestParameter; import org.apache.sling.api.resource.LoginException; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceResolverFactory; import org.osgi.framework.Constants; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.servlet.*; @Component( service = Filter.class, immediate = true, name = "Experience AEM DAM server side duplicate file name check", property = { Constants.SERVICE_RANKING + ":Integer=-99", "sling.filter.scope=COMPONENT", "sling.filter.pattern=((.*.initiateUpload.json)|(.*.createasset.html))", } ) public class DuplicateAssetNameCheck implements Filter { private static Logger log = LoggerFactory.getLogger(DuplicateAssetNameCheck.class); private static String INITIATE_UPLOAD_JSON = ".initiateUpload.json"; private static String CREATE_ASSET_HTML = ".createasset.html"; private static final String EAEM_SERVICE_USER = "eaem-service-user"; @Reference private ResourceResolverFactory factory; @Reference private QueryBuilder builder; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try{ SlingHttpServletRequest slingRequest = (SlingHttpServletRequest)request; SlingHttpServletResponse slingResponse = (SlingHttpServletResponse)response; String uri = slingRequest.getRequestURI(); if(!uri.endsWith(INITIATE_UPLOAD_JSON) && !uri.endsWith(CREATE_ASSET_HTML)){ chain.doFilter(request, response); return; } /*String userAgent = slingRequest.getHeader("User-Agent"); if(isBrowser(userAgent)){ //duplicate filename check in browsers is already done on client side log.info("A Browser User agent : " + userAgent); chain.doFilter(request, response); return; }*/ String fileNames[] = request.getParameterValues("fileName"); if(fileNames == null){ RequestParameter params[] = slingRequest.getRequestParameters("file"); if(ArrayUtils.isEmpty(params)){ log.warn("Skipping duplicate check, 'fileName' and 'file' params, both are empty"); chain.doFilter(request, response); return; } for (final RequestParameter param : params) { if (param.getFileName() == null) { continue; } fileNames = new String[1]; fileNames[0] = param.getFileName(); break; } } if(ArrayUtils.isEmpty(fileNames)){ log.warn("Skipping duplicate check, 'fileName' and 'file' params, both are empty"); chain.doFilter(request, response); return; } List<String> duplicatePaths = getDuplicateFilePaths(factory, builder, fileNames); log.info("duplicatePaths : " + duplicatePaths + ", for file : " + String.join(",", fileNames)); if(!CollectionUtils.isEmpty(duplicatePaths)){ log.info("Duplicate file names detected while upload : " + duplicatePaths); slingResponse.sendError(SlingHttpServletResponse.SC_FORBIDDEN, "Duplicates found: " + String.join(",", duplicatePaths)); return; } chain.doFilter(request, response); }catch(Exception e){ log.error("Error checking for duplicates", e); } } public static List<String> getDuplicateFilePaths(ResourceResolverFactory resourceResolverFactory, QueryBuilder builder, String fileNames[]) throws RepositoryException { ResourceResolver resourceResolver = getServiceResourceResolver(resourceResolverFactory); List<String> duplicates = new ArrayList<String>(); Query query = builder.createQuery(PredicateGroup.create(getQueryPredicateMap(fileNames)), resourceResolver.adaptTo(Session.class)); SearchResult result = query.getResult(); for (Hit hit : result.getHits()) { duplicates.add(hit.getPath()); } return duplicates; } private static Map<String, String> getQueryPredicateMap(String[] fileNames) { Map<String, String> map = new HashMap<>(); map.put("path", "/content/dam"); map.put("type", "dam:Asset"); map.put("group.p.or", "true"); for(int index = 0; index < fileNames.length; index++){ map.put("group." + index + "_nodename", fileNames[index]); } return map; } public static 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 boolean isBrowser(String userAgent){ return (userAgent.contains("Mozilla") || userAgent.contains("Chrome") || userAgent.contains("Safari")); } @Override public void destroy() { } }
No comments:
Post a Comment