AEM | Custom XMP Metadata Writeback

Suman Shekhar Jana
6 min readJul 25, 2023

--

AEM DAM XMP METADATA WRITEBACK

The XMP writeback feature lets you propagate the metadata changes to all or specific renditions of the asset. The feature writes back only those metadata properties that use registered namespaces.

In this blog, we will discuss briefly the OOTB feature available in AEM in the first section. In the second section, we’ll discuss a possible use case of customization and how we can customize it. And finally, in the third section, some approaches & implementation methods that an AEM/Java developer must avoid during development which shall help understand the reason for using the approach discussed in the second section.

Section A: OOTB XMP Metadata Writeback

We have the XMP writeback feature readily available in AEM for use with OOTB DAM Metadata Writeback workflow which comes with a set of predefined parameters. There also exists a launcher to trigger this workflow.

OOTB DAM Metadata Writeback Workflow Launcher

It is recommended to keep this launcher disabled and avoid unnecessary processing unless a requirement arises.

To understand more about this OOTB workflow and play around with it, you could follow this Adobe document (be careful of the AEM version you are using).

A point that the document misses to mention is the formats on which this XMP writeback would work. This is maintained by an OSGI configuration “Day CQ DAM NComm XMP Handler” with PID = “com.day.cq.dam.core.impl.handler. XMP.NCommXMPHandler”.

Here’s a snip of the formats available OOTB where the writeback would work:

In case you need writeback on more file formats, you could add the MIME type in this configuration and test thoroughly. I have tested this with other common file formats for videos, audio, documents, images, spreadsheets, etc and they have worked fine. I could not find any document stating the unsupported formats for writeback specifically, but one should be cognizant of the file sizes, formats they are using, and the computation power the server would require for the writeback process to happen.

Section B: Customizing XMP Writeback

There could be multiple use cases one might have, but writeback implementation could be similar.

Use-case:

A client works with multiple vendors for their online activities and has employed multiple creative designers and artists who help with the creation and modification of assets (images, videos, logos, etc). A vendor has access to view available assets in the AEM DAM (experienced via customized asset share commons) and could request assets from an AEM custodian for their activity. In case the custodian finds this as a legitimate request, he/she shall trigger this one-click custom process where a copy of the files requested would have a unique identifier written back on the original rendition of the assets for future identification, all assets zipped up and shared with the vendor.

Writeback Implementation:

  1. Make sure the OOTB DAM Metadata Writeback workflow launcher is disabled.
  2. Create a custom writeback workflow if the OOTB does not help your use case.
  3. Update OSGI configuration “Day CQ DAM NComm XMP Handler” if required.
  4. Fetch the asset paths, and trigger this custom workflow on these asset paths.
  5. Implement a polling process to verify if the writeback is completed and take any action in case the process failed.
  6. When writeback completes on all the assets, create a zip of these assets, save this ZIP in AEM, and share the ZIP path over an email for download (or as required).

Workflow Model:

Code:

private void runXMPWriteBackProcess(ResourceResolver resolver, String servingAssetPath) throws WorkflowException {
if(null != resolver.getResource(servingAssetPath) && null != resolver.adaptTo(WorkflowSession.class)) {
WorkflowSession workflowSession = resolver.adaptTo(WorkflowSession.class);
if (null != workflowSession) {
WorkflowModel workflowModel = workflowSession.getModel(wsDownloadConfig.getCustomXMPWriteBackWorkflow());
final WorkflowData workflowData = workflowSession.newWorkflowData(PayloadMap.TYPE_JCR_PATH, servingAssetPath);
Map<String, Object> workflowMetadata = new HashMap<>();
workflowMetadata.put("workflowTitle", wsDownloadConfig.getCustomXMPWriteBackWorkflowTitle());
Workflow writeBackWorkflow = workflowSession.startWorkflow(workflowModel, workflowData, workflowMetadata);
if (null != writeBackWorkflow) {
LOG.debug("XMP WriteBack workflow started on payload {}", servingAssetPath);
}
}
}
}
private boolean isStatusFromPollingProcess(ResourceResolver resolver, String servingAssetPath) throws InterruptedException {
LOG.debug("In Polling process");
boolean status = false;
for (int retry=0; retry < wsDownloadConfig.getMaxWriteBackRetryCount(); retry++) {
if(getLatestStatus(resolver, servingAssetPath)) {
status = true;
break;
} else {
LOG.debug("Polling process triggered: retry: {} ! Sleeping for {}", retry, servingAssetPath);
Thread.sleep(3000);
}
}
return status;
}

/*update the resource to be fetched for status based on Set Property workflow process*/
private boolean getLatestStatus(ResourceResolver resolver, String path) {
boolean status = false;
resolver.refresh();
if(null != resolver.getResource(path) && null != resolver.getResource(path).getChild(JcrConstants.JCR_CONTENT)) {
Resource jcrContent = resolver.getResource(path).getChild(JcrConstants.JCR_CONTENT);
ValueMap jcrContentVm = jcrContent.getValueMap();
if(jcrContentVm.containsKey(WSConstants.XMP_WRITE_BACK_COMPLETE)) {
status = StringUtils.equalsIgnoreCase(jcrContentVm.get(WSConstants.XMP_WRITE_BACK_COMPLETE, "false"), "true");
}
}
return status;
}

This implementation process will help to stay close to the original writeback process and help minimize customization errors (more in the next section).

Section C: Do’s & Dont’s

An enthusiastic developer would like to further optimize the implementation, and try to find ways to enhance the code. Here are a few things I tried out, some of which helped me understand the feature better, and some made me realize later to implement as mentioned in Section B.

  1. Understand the OOTB process step: com.day.cq.dam.core.process.XMPWritebackProcess
  2. Find the method or code responsible for writeback (including the considerations added in the code like how XMPMetadata is fetched, and some excluded before writeback action): xmpHandler.writeXmpMetadata(servingAsset, metadata, writeBackOptions);
  3. In the above point, the method accepts com.day.cq.dam.api.handler.xmp.XMPWriteBackOptions
  4. You can create a new interface say CustomXMPWriteBackOptions.java extending the OOTB XMPWriteBackOptions, but it would not cause much of a difference.
Asset servingAsset = servingAssetRes.adaptTo(Asset.class);
if(StringUtils.isNotBlank(servingAsset.getMetadataValue(CustomConstants.CUSTOM_UUID))) {
String customUuid = servingAsset.getMetadataValue(CustomConstants.CUSTOM_UUID);
com.adobe.granite.asset.api.Asset servingGraniteAsset = servingAssetRes.adaptTo(com.adobe.granite.asset.api.Asset.class);
if(null != servingGraniteAsset.getAssetMetadata() && null != servingGraniteAsset.getAssetMetadata().getXMP()) {
XMPMetadata metadata = servingGraniteAsset.getAssetMetadata().getXMP();

if (metadata.get(XMPConst.NS_EXIF_AUX, PS_AUX_ISO) != null) {
metadata.setSimple(XMPConst.NS_EXIF_AUX, PS_AUX_ISO, "");
}
if (metadata.get(XMPConst.NS_EXIF, EXIF_FLASH) != null) {
metadata.remove(XMPConst.NS_EXIF, EXIF_FLASH);
}
if (metadata.get(WSConstants.CUSTOM_NAMESPACE, WSConstants.UUID) != null) {
XMPNode xmpNode = metadata.get(WSConstants.CUSTOM_NAMESPACE, WSConstants.UUID);
LOG.debug("UUID Metadata: {}", xmpNode);
metadata.setSimple(WSConstants.CUSTOM_NAMESPACE, WSConstants.UUID, customUuid);
}

XMPWriteBackOptions writeBackOptions = new XMPWriteBackOptionsImpl();
if(null != servingAsset.getRendition(WSConstants.RENDITION_ORIGINAL)) {
Set<Rendition> xmpWriteBackRenditions = new HashSet<>();
Rendition currentRendition = servingAsset.getRendition(WSConstants.RENDITION_ORIGINAL);
xmpWriteBackRenditions.add(currentRendition);
writeBackOptions.setRenditions(xmpWriteBackRenditions);
}
xmpHandler.writeXmpMetadata(servingAsset, metadata, writeBackOptions);

}
}

Problem 1 (unavailability in Maven Central Repo):
You could try to use the direct implementation by including the dependency in your POM, but unfortunately, you would not find it in the Maven Central Repository

Steps:
1. Go to http://localhost:4502/system/console/depfinder
2. Search for the package: com.day.cq.dam.api.handler.XMP
3. Get the dependency:

<dependency> 
<artifactId>cq-dam-api</artifactId>
<version>6.0.32</version>
<groupId>com.day.cq.dam</groupId>
<scope>provided</scope>
</dependency>

4. Check for the artifact id “cq-dam-API” in the Maven repository (https://mvnrepository.com/artifact/com.day.cq.dam/cq-dam-api)
5. Check the versions and find that 6.0.32 is not present in the Central repository.

Problem 2 (coding standard):
The above code snippet and custom implementation look good, but the problem lies with XMPWriteBackOptions which comes with a “@ProviderType” and must not be implemented outside AEM internal implementation. Even if you implement, you would find your implementation working fine for most of the cases.

Problem 3 (Cloud Manager Deployment):
If you proceed with the custom implementation code (in Section C), the Cloud Manager would identify this as unreliable code and mark this code as an issue with “critical” severity, as it would violate “CQRules:CQBP-84” with description “The product interface com.day.cq.dam.api.handler.xmp.XMPWriteBackOptions annotated with @ProviderType should not be implemented by custom code. Detected in …”.

The Cloud Manager shall also gift a “Reliability Rating” as “D” for the above implementation, and your deployment would be blocked for the AMS Stage and Production environments unless overridden. The Cloud Manager requires all “critical” issues to be fixed to gain a better rating.

Implementing the process mentioned in Section B helped me fix this “Critical” severity issue, which automatically helped me gain a much better reliability rating.

I hope this was helpful for your use case. Please leave a clap if you like it, and I’ll look forward to the comments for improvements in the solution.

--

--

Suman Shekhar Jana
Suman Shekhar Jana

No responses yet