AEM Cloud Service - Assets Replace Binary (only) keeping Metadata

Goal

Action bar button Replace Binary to replace the binary of an asset while keeping Metadata...

(Adobe Experience Manager 2022.11.9832.20221115T164011Z-220900)

Demo | Package Install | Github




Solution

1) Create a servlet apps.eaem.assets.core.servlets.ReplaceBinaryServlet for replacing the binary with the binary of an existing AEM asset. A call to this servlet replaces the binary and executes a ReProcess to regenerate the thumbnails etc.

package apps.eaem.assets.core.servlets;

import com.day.cq.commons.jcr.JcrUtil;
import com.day.cq.contentsync.handler.util.RequestResponseFactory;
import com.google.gson.JsonObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.engine.SlingRequestProcessor;
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.Node;
import javax.jcr.RepositoryException;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component(
service = Servlet.class,
property = {
"sling.servlet.methods=" + HttpConstants.METHOD_POST,
"sling.servlet.resourceTypes=" + "/bin/eaem/replace-binary"})
public class ReplaceBinaryServlet extends SlingAllMethodsServlet {
private final Logger logger = LoggerFactory.getLogger(getClass());

private static final String PROCESS_ASSET_SERVLET = "/bin/asynccommand";
private static final String PROCESS_ASSET_OPERATION = "PROCESS";
private static final String PROCESS_ASSET_PROFILE = "full-process";

@Reference
private SlingRequestProcessor slingRequestProcessor;

@Reference
private RequestResponseFactory requestResponseFactory;

@Override
protected void doPost(final SlingHttpServletRequest req, final SlingHttpServletResponse resp) throws IOException {
resp.setContentType("application/json");
JsonObject responseObject = new JsonObject();

String src = req.getParameter("srcAsset");
String dest = req.getParameter("destAsset");

try{
if(StringUtils.isEmpty(src) || StringUtils.isEmpty(dest)){
responseObject.addProperty("error", "Required parameters missing...");
}else{
replaceOriginalRendition(req.getResourceResolver(), src, dest);
responseObject.addProperty("status", "success");
}
}catch (Exception e){
logger.error("Error replacing binary for {}", src, e);
responseObject.addProperty("error", "Error replacing binary : " + e.getMessage() );
}

resp.getWriter().write(responseObject.toString());
}

public boolean replaceOriginalRendition(ResourceResolver resolver, String sourceAssetPath, String destAssetPath) {
Resource sourceAssetOrig = resolver.getResource(sourceAssetPath + "/jcr:content/renditions/original");
Resource destAssetParent = resolver.getResource(destAssetPath + "/jcr:content/renditions");
boolean replaceSuccess = false;

try {
destAssetParent.getChild("original").adaptTo(Node.class).remove();

JcrUtil.copy(sourceAssetOrig.adaptTo(Node.class), destAssetParent.adaptTo(Node.class), null);

resolver.commit();

runAssetReProcess(resolver, destAssetPath);

replaceSuccess = true;
}catch (RepositoryException | PersistenceException e){
logger.error("Error replacing original rendition for {}", sourceAssetPath, e);
replaceSuccess = false;
}

return replaceSuccess;
}

public boolean runAssetReProcess(ResourceResolver resolver, String assetPath){
boolean reprocessSuccess = true;
logger.debug("Starting Reprocess of Asset {}", assetPath);

Map<String, Object> requestParams = new HashMap<String, Object>();
requestParams.put("operation", PROCESS_ASSET_OPERATION);
requestParams.put("profile-select", PROCESS_ASSET_PROFILE);
requestParams.put("runPostProcess", "false");
requestParams.put("description", "Reprocessing asset - " + assetPath);
requestParams.put("asset", assetPath);

HttpServletRequest request = requestResponseFactory.createRequest("POST", PROCESS_ASSET_SERVLET, requestParams);
ByteArrayOutputStream bos = new ByteArrayOutputStream();

HttpServletResponse response = this.requestResponseFactory.createResponse(bos);

try {
slingRequestProcessor.processRequest(request, response, resolver);
}catch (Exception e){
logger.error("Error while reprocessing {} ", assetPath, e);
reprocessSuccess = false;
}

return reprocessSuccess;
}
}


2) Create a content api node eaem-assets-replace-binary\ui.content\src\main\content\jcr_root\content\api\.content.xml with the servlet resource type /bin/eaem/replace-binary

<?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"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured">
<eaem jcr:primaryType="nt:unstructured">
<replace-binary
jcr:primaryType="nt:unstructured"
sling:resourceType="/bin/eaem/replace-binary"/>
</eaem>
</jcr:root>


3) Add the Replace Binary button configuration in ui.apps\src\main\content\jcr_root\apps\eaem-assets-replace-binary\clientlibs\replace-binary\content\replace-binary-but\.content.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"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/collection/action"
icon="refresh"
target=".cq-damadmin-admin-childpages"
text="Replace Binary"
variant="actionBar"/>


4) Add a form configuration to show the modal for selecting source asset in ui.apps\src\main\content\jcr_root\apps\eaem-assets-replace-binary\clientlibs\replace-binary\replace-binary-dialog\.content.xml

<?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="Replace Binary"
sling:resourceType="granite/ui/components/coral/foundation/page">
<head jcr:primaryType="nt:unstructured">
<favicon
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/page/favicon"/>
<viewport
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/admin/page/viewport"/>
<clientlibs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
categories="[coralui3,granite.ui.coral.foundation,granite.ui.shell,dam.gui.admin.coral,eaem.replace.binary]"/>
</head>
<body
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/page/body">
<items jcr:primaryType="nt:unstructured">
<content
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form"
action="/content/api/eaem/replace-binary"
foundationForm="{Boolean}true"
maximized="{Boolean}true"
method="post"
novalidate="{Boolean}true"
style="vertical">
<items jcr:primaryType="nt:unstructured">
<wizard
jcr:primaryType="nt:unstructured"
jcr:title="Replace Binary"
sling:resourceType="granite/ui/components/coral/foundation/wizard">
<items jcr:primaryType="nt:unstructured">
<container
granite:class="eaem-replace-binary-form"
jcr:primaryType="nt:unstructured"
jcr:title="Replace Binary"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<actionbar
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<srcAsset
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
forceSelection="true"
fieldDescription="Path of Asset to be used as source binary "
fieldLabel="Source Asset"
rootPath="/content/dam"
name="srcAsset"/>
</items>
</actionbar>
</items>
<parentConfig jcr:primaryType="nt:unstructured">
<prev
granite:class="foundation-wizard-control"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/anchorbutton"
text="Cancel">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="cancel"/>
</prev>
<next
granite:class="foundation-wizard-control"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
text="Replace"
type="submit"
variant="primary">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="next"/>
</next>
</parentConfig>
</container>
</items>
</wizard>
</items>
</content>
</items>
</body>
</jcr:content>
</jcr:root>


5) Add a clientlib to provide runtime behavior like adding the button to action bar, show modal for source asset selection etc. in ui.apps\src\main\content\jcr_root\apps\eaem-assets-replace-binary\clientlibs\replace-binary\clientlib\.content.xml

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:ClientLibraryFolder"
categories="[dam.gui.actions.coral,eaem.replace.binary]"
dependencies="eaem.lodash"/>


6) Add the JS file in ui.apps\src\main\content\jcr_root\apps\eaem-assets-replace-binary\clientlibs\replace-binary\clientlib\replace-binary.js

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

var ASSET_DETAILS_PAGE = "/assetdetails.html",
BESIDE_ACTIVATOR = "cq-damadmin-admin-actions-download-activator",
REPLACE_BINARY_BUT_URL = "/apps/eaem-assets-replace-binary/clientlibs/replace-binary/content/replace-binaryl-but.html",
REPLACE_BINARY_URL = "/apps/eaem-assets-replace-binary/clientlibs/replace-binary/replace-binary-dialog.html",
CANCEL_CSS = "[data-foundation-wizard-control-action='cancel']",
DEST_ASSET_FIELD = "destAsset",
SRC_ASSET_FIELD_SEL = "[name='srcAsset']",
url = document.location.pathname,
initialized = false, $replaceBinaryModal;

if (isAssetDetailsPage()) {
$document.on("foundation-contentloaded", addActionBarButtons);
}else if(url.indexOf(REPLACE_BINARY_URL) == 0){
$document.on("foundation-contentloaded", handleReplaceBinaryDialog);
$document.on("click", CANCEL_CSS, sendCancelMessage);
$document.submit( () => {
const fui = $(window).adaptTo("foundation-ui");
let srcAsset = $ (SRC_ASSET_FIELD_SEL).find("input").val();

if(!srcAsset){
fui.alert("Error","Select the source asset", "error");
}else{
fui.alert("Success","Replace binary action initiated. You'll be notified on successful completion");
setTimeout(sendCancelMessage, 3000);
}
});
}

function addActionBarButtons(){
if (initialized) {
return;
}

initialized = true;

$.ajax(REPLACE_BINARY_BUT_URL).done(addReplaceBinaryButton);
}

function addReplaceBinaryButton(html){
var $eActivator = $("." + BESIDE_ACTIVATOR);

if ($eActivator.length == 0) {
return;
}

var $replaceBinaryBut = $(html).insertAfter($eActivator);

$replaceBinaryBut.find("coral-button-label").css("padding-left", "7px");
$replaceBinaryBut.click(openReplaceBinaryModal);
$(window).off('message', closeReplaceBinaryModal).on('message', closeReplaceBinaryModal);
}

function handleReplaceBinaryDialog(){
var $form = $("form");

$('<input />').attr('type', 'hidden').attr('name', DEST_ASSET_FIELD)
.attr('value', getParent().location.pathname.substring(ASSET_DETAILS_PAGE.length)).appendTo($form);
}

function sendCancelMessage(){
var message = {
action: "cancel"
};

getParent().postMessage(JSON.stringify(message), "*");
}

function openReplaceBinaryModal(){
var $iframe = $('<iframe>'),
$modal = $('<div>').addClass('eaem-replace-binary-apply-modal coral-Modal');

$iframe.attr('src', REPLACE_BINARY_URL).appendTo($modal);

$modal.appendTo('body').modal({
type: 'default',
buttons: [],
visible: true
});

$replaceBinaryModal = $modal;
}

function getParent() {
if (window.opener) {
return window.opener;
}

return parent;
}

function closeReplaceBinaryModal(event){
event = event.originalEvent || {};

if (_.isEmpty(event.data) || _.isEmpty($replaceBinaryModal)) {
return;
}

var message;

try { message = JSON.parse(event.data); }catch(e){ return; }

if (!message || message.action !== "cancel") {
return;
}

var modal = $replaceBinaryModal.data('modal');
modal.hide();
modal.$element.remove();
}

function isAssetDetailsPage() {
return (window.location.pathname.indexOf(ASSET_DETAILS_PAGE) >= 0);
}
}(jQuery, jQuery(document)));


No comments:

Post a Comment