Goal
Replace the binary (not metadata) of an AEM Asset with binary of another AEM Asset or Local File Upload
Thank you Rahul Singh Rawat, Drew Robinson for the code snippets
Demo | Package Install | Source Code | Git Hub
Replace Options
Replace with AEM File
Solution
1) Create a servlet apps.experienceaem.assets.ReplaceBinary with following code for performing the binary replace by adding original rendition
package apps.experienceaem.assets; import com.day.cq.dam.api.Asset; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Properties; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Service; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.servlets.SlingAllMethodsServlet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jcr.Session; import javax.servlet.ServletException; import java.io.InputStream; @Component(metatype = true, label = "Experience AEM Replace Binary Servlet") @Service @Properties({ @Property(name = "sling.servlet.methods", value = { "POST" }, propertyPrivate = true), @Property(name = "sling.servlet.paths", value = "/bin/eaem/replace-binary", propertyPrivate = true) }) public class ReplaceBinary extends SlingAllMethodsServlet { private static final Logger log = LoggerFactory.getLogger(ReplaceBinary.class); @Override protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException { ResourceResolver resourceResolver = request.getResourceResolver(); String toBeReplacedAssetPath = request.getParameter("toBeReplacedAssetPath"); String replaceWithAssetPath = request.getParameter("replaceWithAssetPath"); String deleteSource = request.getParameter("deleteSource"); replaceBinary(resourceResolver, toBeReplacedAssetPath, replaceWithAssetPath, deleteSource); } private void replaceBinary(ResourceResolver resourceResolver, String toBeReplacedAssetPath, String replaceWithAssetPath, String deleteSource) { Resource toBeReplacedResource = resourceResolver.getResource(toBeReplacedAssetPath); Resource replacingResource = resourceResolver.getResource(replaceWithAssetPath); Asset toBeReplacedAsset = toBeReplacedResource.adaptTo(Asset.class); Asset replacingAsset = replacingResource.adaptTo(Asset.class); String mimeType = toBeReplacedAsset.getMimeType(); Resource original = replacingAsset.getOriginal(); InputStream stream = original.adaptTo(InputStream.class); toBeReplacedAsset.addRendition("original", stream, mimeType); if(!"true".equals(deleteSource)){ return; } try{ Session session = resourceResolver.adaptTo(Session.class); session.removeItem(replacingResource.getPath()); session.save(); }catch(Exception e){ log.warn("Error removing asset - " + replacingResource.getPath()); } } }
2) Create nt:unstructured node /apps/eaem-assets-replace-binary/button/replace-binary with the following xml for generating pull down html, added to Action Bar, showing the Replace options - Replace with Local File, Replace with AEM File
<?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" granite:id="eaem-binary-replace" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/pulldown" icon="globe" text="Replace" variant="actionBar"> <items jcr:primaryType="nt:unstructured"> <replace-with-local-file granite:class="replace-with-local-file" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/collection/actionlink" target=".eaem-placeholder" text="Replace with Local File"/> <replace-with-aem-file granite:class="replace-with-aem-file" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/collection/actionlink" target=".eaem-placeholder" text="Replace with AEM File"/> </items> </jcr:root>
3) Create nt:unstructured node /apps/eaem-assets-replace-binary/button/replace-with-local-fileupload with following xml, for generating the file upload button html added to action bar (as hidden, to workaround the IE/Firefox issue)
<?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" granite:id="replace-with-local-fileupload" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/fileupload" accept="image/*" async="{Boolean}true" autoStart="{Boolean}false" chunkuploadsupported="{Boolean}true" multiple="{Boolean}false" name="file"/>
4) Create the wizard /apps/eaem-assets-replace-binary/wizard/select-assets of type cq:Page with following xml, to select an AEM Asset (source binary) for the Replace with AEM File option
<?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:primaryType="cq:Page"> <jcr:content jcr:mixinTypes="[sling:VanityPath]" jcr:primaryType="nt:unstructured" jcr:title="Select Replacement File" sling:resourceType="granite/ui/components/coral/foundation/page"> <head jcr:primaryType="nt:unstructured"> <viewport jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/admin/page/viewport"/> <favicon jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/page/favicon"/> <clientlibs jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs" categories="[coralui3,granite.ui.coral.foundation,cq.listview.coral.columns.personalization,dam.gui.admin.util]"/> </head> <body jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/page/body"> <items jcr:primaryType="nt:unstructured"> <form jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form" foundationForm="{Boolean}true" maximized="{Boolean}true" method="post" novalidate="{Boolean}true" style="vertical"> <items jcr:primaryType="nt:unstructured"> <charset jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/hidden" name="_charset_" value="utf-8"/> <wizard jcr:primaryType="nt:unstructured" jcr:title="Select Replacement File" sling:resourceType="granite/ui/components/coral/foundation/wizard"> <items jcr:primaryType="nt:unstructured"> <step1 jcr:primaryType="nt:unstructured" jcr:title="Select Assets" sling:resourceType="granite/ui/components/coral/foundation/container"> <items jcr:primaryType="nt:unstructured"> <column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/include" path="/libs/dam/gui/content/assets/jcr:content/views/column"/> </items> <parentConfig jcr:primaryType="nt:unstructured"> <next granite:class="foundation-wizard-control" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/button" text="Next" variant="primary"> <granite:data jcr:primaryType="nt:unstructured" foundation-wizard-control-action="next"/> </next> </parentConfig> </step1> <step2 jcr:primaryType="nt:unstructured" jcr:title="Confirm" sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"> <parentConfig jcr:primaryType="nt:unstructured"> <next granite:class="foundation-wizard-control" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/button" text="Replace" variant="primary"> <granite:data jcr:primaryType="nt:unstructured" foundation-wizard-control-action="next"/> </next> </parentConfig> <items jcr:primaryType="nt:unstructured"> <column1 jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/container"> <items jcr:primaryType="nt:unstructured"> <text1 jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/heading" level="{Long}3" text="you are going to replace 'PLACEHOLDER_SRC' with 'PLACEHOLDER_DEST'"/> <text2 jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/heading" level="{Long}3" text="Select 'Replace' to confirm."/> </items> </column1> </items> </step2> </items> <granite:data jcr:primaryType="nt:unstructured" suffix="${requestPathInfo.suffix}"/> </wizard> </items> </form> </items> </body> </jcr:content> </jcr:root>
5) Create /apps/eaem-assets-replace-binary/clientlib of type cq:ClientLibraryFolder with categories dam.gui.admin.util and dependencies underscore
6) Create file /apps/eaem-assets-replace-binary/clientlib/js.txt with the following content
replace-binary.js
7) Create file /apps/eaem-assets-replace-binary/clientlib/replace-binary.js with the following code
(function ($, $document) { var FOUNDATION_CONTENT_LOADED = "foundation-contentloaded", FOUNDATION_MODE_CHANGE = "foundation-mode-change", FOUNDATION_SELECTIONS_CHANGE = "foundation-selections-change", FOUNDATION_WIZARD_STEPCHANGE = "foundation-wizard-stepchange", EDIT_ACTIVATOR = "aem-assets-admin-actions-edit-activator", REPLACE_WITH_LOCAL_FILE = ".replace-with-local-file", REPLACE_WITH_LOCAL_FILE_UPLOAD = "#replace-with-local-fileupload", REPLACE_WITH_AEM_FILE = ".replace-with-aem-file", EAEM_BINARY_REPLACE = "#eaem-binary-replace", DAM_ADMIN_CHILD_PAGES = ".cq-damadmin-admin-childpages", PLACEHOLDER_DEST = "PLACEHOLDER_DEST", PLACEHOLDER_SRC = "PLACEHOLDER_SRC", REPLACE_WITH_ASSET_PATH = "replaceWithAssetPath", TO_BE_REPLACED_ASSET_PATH = "toBeReplacedAssetPath", DELETE_SOURCE = "deleteSource", fui = $(window).adaptTo("foundation-ui"), SUBMIT_URL = "/bin/eaem/replace-binary?", SELECT_ASSET_URL = "/apps/eaem-assets-replace-binary/wizard/select-assets.html", REPLACE_PULL_DOWN_URL = "/apps/eaem-assets-replace-binary/button/replace-binary.html", FILE_UPLOAD_BUT_URL = "/apps/eaem-assets-replace-binary/button/replace-with-local-fileupload.html"; var pathName = window.location.pathname; if(pathName.indexOf("/assets.html") === 0){ handleAssetsConsole(); }else if (pathName.indexOf(SELECT_ASSET_URL) === 0){ handleSelectAssetsWizard(); } function handleSelectAssetsWizard(){ var replaceWithAssetPath, submitHandlerAdded = false, toBeReplacedAssetPath = queryParameters()[TO_BE_REPLACED_ASSET_PATH] ; $document.on(FOUNDATION_CONTENT_LOADED, handleFirstStep); function handleFirstStep(){ var stepNumber = getWizardStepNumber(); if(stepNumber !== 1){ return; } var $cancelBut = getWizardCancelButton(); $cancelBut.on("click", function(){ window.parent.location.reload(); }); $document.on(FOUNDATION_SELECTIONS_CHANGE, ".foundation-collection", disableNextIfNoAssetsSelected); $document.on(FOUNDATION_WIZARD_STEPCHANGE, handleWizardSteps); } function handleWizardSteps(event, nextStep){ var stepNumber = getWizardStepNumber(); if(stepNumber !== 2){ return; } var $nextStep = $(nextStep), html = $nextStep.html(), dest = getStringAfterLastSlash(replaceWithAssetPath), src = getStringAfterLastSlash(toBeReplacedAssetPath); $nextStep.html(html.replace(PLACEHOLDER_DEST, dest).replace(PLACEHOLDER_SRC, src)); if(submitHandlerAdded){ return; } submitHandlerAdded = true; var $nextButton = getCurrentStepNextButton(); $nextButton.on("click", registerSubmitHandler); } function registerSubmitHandler(){ var url = SUBMIT_URL + REPLACE_WITH_ASSET_PATH + "=" + replaceWithAssetPath + "&" + TO_BE_REPLACED_ASSET_PATH + "=" + toBeReplacedAssetPath; fui.wait(); $.ajax({url : url, type: "POST"}).done(function(){ fui.clearWait(); var dest = getStringAfterLastSlash(replaceWithAssetPath), src = getStringAfterLastSlash(toBeReplacedAssetPath); showAlert("File '" + src + "' replaced with '" + dest + "' binary", 'Replaced', callback); }); function callback(){ window.parent.location.reload(); } } function disableNextIfNoAssetsSelected(){ var stepNumber = getWizardStepNumber(); if(stepNumber !== 1){ return; } disableWizardNext(); var fSelections = $(".foundation-collection").adaptTo("foundation-selections"); if(fSelections && (fSelections.count() > 1)){ showAlert("Select one asset", 'Error'); return; } replaceWithAssetPath = getToBeReplacedPaths(); replaceWithAssetPath = _.isEmpty(replaceWithAssetPath) ? "" : replaceWithAssetPath[0]; if(_.isEmpty(replaceWithAssetPath) || _.isEmpty(toBeReplacedAssetPath)){ return; } var destExtn = getStringAfterLastDot(replaceWithAssetPath), srcExtn = getStringAfterLastDot(toBeReplacedAssetPath); if (destExtn !== srcExtn) { var dest = decodeURIComponent(getStringAfterLastSlash(replaceWithAssetPath)), src = decodeURIComponent(getStringAfterLastSlash(toBeReplacedAssetPath)); showAlert("'" + dest + "' and '" + src + "' donot have the same extension", 'Error'); replaceWithAssetPath = ""; return; } if(!_.isEmpty(replaceWithAssetPath)){ enableWizardNext(); } } function disableWizardNext(){ toggleWizard(false); } function enableWizardNext(){ toggleWizard(true); } function toggleWizard(isEnable){ var $wizard = $(".foundation-wizard"); if(_.isEmpty($wizard)){ return; } var wizardApi = $wizard.adaptTo("foundation-wizard"); wizardApi.toggleNext(isEnable); } function getWizardCancelButton(){ var $wizard = $(".foundation-wizard"); return $($wizard.find("[data-foundation-wizard-control-action=cancel]")[0]); } function getWizardStepNumber(){ var $wizard = $(".foundation-wizard"), currentStep = $wizard.find(".foundation-wizard-step-active"), wizardApi = $wizard.adaptTo("foundation-wizard"); return wizardApi.getPrevSteps(currentStep).length + 1; } function getCurrentStepNextButton(){ var stepNumber = getWizardStepNumber(), $wizard = $(".foundation-wizard"); return $($wizard.find("[data-foundation-wizard-control-action=next]")[stepNumber - 1]); } } function handleAssetsConsole(){ var replaceDialog = null, folderPath; $document.on(FOUNDATION_MODE_CHANGE, function(e, mode){ if(mode !== "selection" ){ return; } var $replaceButton = $(EAEM_BINARY_REPLACE); if(!_.isEmpty($replaceButton)){ return; } $.ajax(REPLACE_PULL_DOWN_URL).done(addPullDown); $.ajax(FILE_UPLOAD_BUT_URL).done(addFileUpload); }); function addFileUpload(html){ var $abContainer = $("coral-actionbar-container:first"), $childPage = $(DAM_ADMIN_CHILD_PAGES), folderPath = $childPage.data("foundation-collection-id"); var $fileUpload = $(html).appendTo($abContainer).attr("hidden", "hidden"); $fileUpload.off('coral-fileupload:fileadded') .on('coral-fileupload:fileadded', uploadHandler) .off('coral-fileupload:loadend') .on('coral-fileupload:loadend', fileUploaded); function fileUploaded(){ var replaceWithAssetPath = folderPath + "/" + getStringAfterLastSlash(this.value), toBeReplacedAssetPath = getToBeReplacedPaths()[0]; var url = SUBMIT_URL + REPLACE_WITH_ASSET_PATH + "=" + replaceWithAssetPath + "&" + TO_BE_REPLACED_ASSET_PATH + "=" + toBeReplacedAssetPath + "&" + DELETE_SOURCE + "=true"; fui.wait(); $.ajax({url : url, type: "POST"}).done(function(){ fui.clearWait(); var dest = getStringAfterLastSlash(replaceWithAssetPath), src = getStringAfterLastSlash(toBeReplacedAssetPath); showAlert("File '" + src + "' replaced with '" + dest + "' binary", 'Replaced', function(){ window.location.reload(); }); }); } function uploadHandler(){ var toBeReplacedAssetPath = getToBeReplacedPaths()[0], replaceWithAssetPath = this.value, destExtn = getStringAfterLastDot(toBeReplacedAssetPath), srcExtn = getStringAfterLastDot(replaceWithAssetPath); if (destExtn !== srcExtn) { var dest = decodeURIComponent(getStringAfterLastSlash(toBeReplacedAssetPath)), src = decodeURIComponent(getStringAfterLastSlash(replaceWithAssetPath)); showAlert("'" + dest + "' and '" + src + "' donot have the same extension", 'Error'); return; } this.action = folderPath + ".createasset.html"; this.upload(); } } function addPullDown(html){ var $eActivator = $("." + EDIT_ACTIVATOR); if ($eActivator.length == 0) { return; } $(html).insertBefore( $eActivator ); handleReplaceWithLocalFile(); handleReplaceWithAEMFile(); } function handleReplaceWithLocalFile(){ var $replaceWithLocalFile = $(REPLACE_WITH_LOCAL_FILE); $replaceWithLocalFile.click(function(event){ event.preventDefault(); var toBeReplacedAssetPath = getToBeReplacedPaths(); if(toBeReplacedAssetPath.length > 1){ showAlert("Select one asset...", "Error"); return; } //upload not added in pulldown to workaround IE11/firefox issue $(REPLACE_WITH_LOCAL_FILE_UPLOAD).find("button").click(); }); } function handleReplaceWithAEMFile(){ var $childPage = $(DAM_ADMIN_CHILD_PAGES), foundationLayout = $childPage.data("foundation-layout"), $replaceAEMFile = $(REPLACE_WITH_AEM_FILE); if(_.isEmpty(foundationLayout)){ return; } folderPath = $childPage.data("foundation-collection-id"); $replaceAEMFile.click(function(event){ event.preventDefault(); var toBeReplacedAssetPath = getToBeReplacedPaths(); if(toBeReplacedAssetPath.length > 1){ showAlert("Select one asset...", "Error"); return; } replaceDialog = getReplaceAEMFileDialog(folderPath + "?toBeReplacedAssetPath=" + toBeReplacedAssetPath[0]); replaceDialog.show(); }); } function getReplaceAEMFileDialog(path){ return new Coral.Dialog().set({ closable: "on", header: { innerHTML: 'File Replace' }, content: { innerHTML: getReplaceAEMFileDialogContent(path) } }); } function getReplaceAEMFileDialogContent(path){ var url = SELECT_ASSET_URL + path; return "<iframe width='1300px' height='700px' frameBorder='0' src='" + url + "'></iframe>"; } } function getToBeReplacedPaths(){ var toBeReplacedAssetPaths = []; $(".foundation-selections-item").each(function(index, asset){ toBeReplacedAssetPaths.push($(asset).data("foundation-collection-item-id")); }); return toBeReplacedAssetPaths; } function getStringAfterLastSlash(str){ if(!str){ return ""; } var find = ""; if(str.indexOf("/") !== -1){ find = "/"; }else if(str.indexOf("\\") !== -1){ find = "\\"; } return str.substr(str.lastIndexOf(find) + 1); } function getStringAfterLastDot(str){ if(!str || (str.indexOf(".") == -1)){ return ""; } return str.substr(str.lastIndexOf(".") + 1); } 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, "default", options, callback); } function queryParameters(searchStr) { var result = {}, param, params = (searchStr ? searchStr.split(/\?|\&/) : document.location.search.split(/\?|\&/)); params.forEach( function(it) { if (_.isEmpty(it)) { return; } param = it.split("="); result[param[0]] = param[1]; }); return result; } }($, $(document)));
Hi Sreekanth, i need to disable/remove the "Replace" button, when the user tries to upload an asset that is already present in the folder, they are presented with a "Name conflict" pop-up that has the following buttons 1) "cancel" 2) "Keep Both" 3) "Replace" 4) "create version". i want to disable/remove the "replace" button. any pointers?
ReplyDelete