Background: We are trying to cleanup the DAM and there are som many assets that got migrated from elsewhere, from what i know Assets were literally dumped in the AEM DAM.
Task: We are trying to cleanup the DAM by moving very old and unused assets to a archive folder.
Issue: In order to do so we have used ACS commons Bulk Move Operation from the Renovator and also created a custom Asset Move operation using Granite Asset API.
[1]. ACS commons - Shows the successful Dry run and breaks in between due to some NameSpace not existing issue. and dose not updates what assets got moved - which leads again a manual process to do this activity.
[2]. Cannot Use OOTB move operations as we have more than 20k assets from different location.
3. Created a Custom Move Operation which uses the features and overcomes the drawback of [1] and [2]. Provides the report of the assets that did not move and dose not break the job in between if any asset failed to move.
Question: My question here is I can optimize this custom solution more by identifying the assets that have non existing namespace in AEM and probably delete them from the source location without moving them around or create a list of those assets.
The Most Important question here is how to analyse any asset if they have non-existing namespace also I don't have the list of non-existing namespaces so that we should not rely on the predefined list.
Please provide your valuable insights on this.
Topics help categorize Community content and increase your ability to discover relevant content.
Views
Replies
Total Likes
hi @kr_Saurabh1707,
I cannot think of anything quick and out of the box. Here's what I would do.
First get the list of namespaces:
Next, I would create a package to download all asset metadata (excluding renditions) using the following filter settings:
Root path: /content/dam/your/root
Rules: Exclude=.*/renditions/.*
After extracting the package contents, run the following bash command to find all used namespaces:
grep -R -e "[a-zA-Z0-9]*:[a-zA-Z0-9]*=" | cut -d: -f2 | sed -e 's/^[[:space:]]*//' | grep -v "<" | grep -v Binary | sort -u
Result:
cq
crs
dam
dc
exif
exifEX
Iptc4xmpCore
jcr
photoshop
psAux
sling
stEvt
stRef
tiff
xmp
xmpMM
xmpRights
To identify missing namespaces, compare the list of used namespaces with all available namespaces in your instance, found under “/jcr:system/rep:namespaces”. You can then execute a command to find assets with the missing namespaces.
grep -R xmp:
The command will return both the path and the property's key-value pair.
./jcr_root/content/dam/we-retail/en/activities/hiking/hiking_2.jpg/.content.xml: xmp:CreateDate="{Date}2015-07-22T11:47:36.000-07:00"
./jcr_root/content/dam/we-retail/en/activities/hiking/hiking_2.jpg/.content.xml: xmp:CreatorTool="Adobe Photoshop CC 2015 (Macintosh)"
./jcr_root/content/dam/we-retail/en/activities/hiking/hiking_2.jpg/.content.xml: xmp:MetadataDate="{Date}2016-01-17T11:33:08.000-05:00"
./jcr_root/content/dam/we-retail/en/activities/hiking/hiking_2.jpg/.content.xml: xmp:ModifyDate="{Date}2016-01-17T11:33:08.000+01:00"
./jcr_root/content/dam/we-retail/en/activities/hiking/hiking_4.jpg/.content.xml: xmp:CreateDate="{Date}2015-07-22T11:48:48.000-07:00"
./jcr_root/content/dam/we-retail/en/activities/hiking/hiking_4.jpg/.content.xml: xmp:CreatorTool="Adobe Photoshop CC 2015 (Macintosh)"
./jcr_root/content/dam/we-retail/en/activities/hiking/hiking_4.jpg/.content.xml: xmp:MetadataDate="{Date}2016-01-14T10:58:11.000-08:00"
./jcr_root/content/dam/we-retail/en/activities/hiking/hiking_4.jpg/.content.xml: xmp:ModifyDate="{Date}2016-01-14T10:58:11.000+01:00"
This can be easily converted into a CSV file to be used as input. Now, you can create a custom workflow step that processes each CSV entry by deleting the property with the missing namespace from the asset.
Excerpt code to delete a property (AI generated):
/**
* Deletes a specified property from a JCR node.
*
* @param resourceResolver The ResourceResolver instance to access the JCR.
* @param nodePath The JCR path to the node (e.g., "/content/dam/myasset/jcr:content/metadata").
* @param propertyName The name of the property to delete (e.g., "my:customProperty").
* @return true if the property was found and deleted, false otherwise.
*/
public boolean deleteProperty(ResourceResolver resourceResolver, String nodePath, String propertyName) {
if (resourceResolver == null || nodePath == null || propertyName == null || nodePath.isEmpty() || propertyName.isEmpty()) {
LOG.warn("Invalid input: resourceResolver, nodePath, or propertyName cannot be null or empty.");
return false;
}
Session session = null;
try {
// 1. Adapt ResourceResolver to JCR Session
session = resourceResolver.adaptTo(Session.class);
if (session == null) {
LOG.error("Could not adapt ResourceResolver to JCR Session.");
return false;
}
// 2. Check if the node exists
if (!session.nodeExists(nodePath)) {
LOG.warn("Node does not exist at path: {}", nodePath);
return false;
}
// 3. Get the Node
Node node = session.getNode(nodePath);
// 4. Check if the property exists on the node
if (!node.hasProperty(propertyName)) {
LOG.info("Property '{}' does not exist on node '{}'. No action taken.", propertyName, nodePath);
return false;
}
// 5. Delete the property
node.getProperty(propertyName).remove();
LOG.debug("Attempting to remove property '{}' from node '{}'.", propertyName, nodePath);
// 6. Save the changes to the repository
session.save();
LOG.info("Successfully deleted property '{}' from node '{}'.", propertyName, nodePath);
return true;
} catch (RepositoryException e) {
LOG.error("RepositoryException occurred while deleting property '{}' from node '{}': {}",
propertyName, nodePath, e.getMessage(), e);
// In case of an error, it's good practice to revert pending changes
if (session != null && session.hasPendingChanges()) {
try {
session.refresh(false); // Discard all pending changes
LOG.info("Discarded pending changes due to error.");
} catch (RepositoryException refreshEx) {
LOG.error("Error refreshing session after deletion failure: {}", refreshEx.getMessage(), refreshEx);
}
}
return false;
} finally {
// 7. Close the ResourceResolver (and indirectly the Session)
// If the ResourceResolver was obtained for this specific operation, close it.
// If it was passed from an external service that manages its lifecycle, do not close it here.
// For most Sling services, you'd want to close the resolver when you're done with it.
if (resourceResolver != null && resourceResolver.isLive()) {
// The ResourceResolver.close() will also close the associated JCR Session
resourceResolver.close();
LOG.debug("ResourceResolver closed.");
}
}
}
Hi @kr_Saurabh1707 ,
Try below steps:
Step 1: Get the List of Registered Namespaces in AEM
Use CRXDE or JCR Explorer:
Path:
/jcr:system/rep:namespaces
Each namespace appears like:
<ns:dc xmlns:ns="http://www.jcp.org/jcr/1.0">dc</ns:dc>
<ns:dam xmlns:ns="http://www.adobe.com/dam/1.0">dam</ns:dam>
Export this to a valid-namespaces.txt file.
Step 2: Export DAM Metadata (excluding renditions)
Use VLT / AEM Package Manager:
- Root path: /content/dam/
- Exclude filter: .*renditions.*
Or use JCR query with resourceResolver.findResources() to programmatically loop through and collect only .metadata nodes.
Step 3: Extract Used Namespaces
You can now parse the content of .content.xml files or use NodeIterator in AEM Java code:
Option A: Bash (if using package)
grep -R "[a-zA-Z0-9]*:" ./jcr_root/content/dam | cut -d: -f2 | sed -E 's/^\s+//' | cut -d: -f1 | sort -u > used-namespaces.txt
Option B: Java – extract prefixes
public Set<String> extractNamespaces(Resource metadata) {
Set<String> usedNamespaces = new HashSet<>();
ValueMap props = metadata.getValueMap();
for (String key : props.keySet()) {
if (key.contains(":")) {
String prefix = key.split(":")[0];
usedNamespaces.add(prefix);
}
}
return usedNamespaces;
}
Step 4: Compare Used vs Registered
You now have:
- used-namespaces.txt
- valid-namespaces.txt
Run:
comm -23 used-namespaces.txt valid-namespaces.txt > missing-namespaces.txt
This gives you the unregistered / unknown namespaces used in DAM assets.
Step 5: Identify Assets with Unregistered Namespaces
You can now build a report via Java:
Java Logic (in a workflow or service)
public List<String> findAssetsWithInvalidNamespaces(ResourceResolver resolver, Set<String> invalidNamespaces) {
List<String> invalidAssets = new ArrayList<>();
Iterator<Resource> assets = resolver.findResources("SELECT * FROM [dam:Asset]", Query.JCR_SQL2);
while (assets.hasNext()) {
Resource asset = assets.next();
Resource metadata = asset.getChild("jcr:content/metadata");
if (metadata != null) {
ValueMap vm = metadata.getValueMap();
for (String key : vm.keySet()) {
if (key.contains(":")) {
String ns = key.split(":")[0];
if (invalidNamespaces.contains(ns)) {
invalidAssets.add(asset.getPath());
break; // Skip after first invalid namespace found
}
}
}
}
}
return invalidAssets;
}
Step 6: Clean or Report
Now that you have the list of invalid assets:
Generate CSV report for manual review
Or automate deletion of invalid properties:
if (node.hasProperty("invalid:property")) {
node.getProperty("invalid:property").remove();
}
Or skip assets altogether during your custom bulk move logic.
Regards,
Amit
Views
Replies
Total Likes
Just checking in — were you able to resolve your issue?
We’d love to hear how things worked out. If the suggestions above helped, marking a response as correct can guide others with similar questions. And if you found another solution, feel free to share it — your insights could really benefit the community. Thanks again for being part of the conversation!
Views
Replies
Total Likes
Views
Likes
Replies
Views
Likes
Replies