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)));