Stop running AEM queries on AEMaaCS. Instead...

Saravana Prakash
2 min readDec 19, 2023

--

Photo by Peter Herrmann on Unsplash

Prelude:

QueryBuilder has been the defacto standard for searching JCR for long time. But JCR queries has this limitation of maximum number of nodes to traverse. And its not correct to simply keep pushing the limit.

Jörg Hoh long back pointed an interesting alternate to avoid JCR queries and work at higher layer of abstraction: Sling. Dan Klco voted the same in his article.

Sling ResourceStream offers a much faster, optimized, verbose, readable API, that developers dont need to learn XPath or SQL2 syntax.

Requirement:

  • I am working on migrating assets into AEM.
  • Before uploading an asset, I have to search if asset in same name is already imported under different DAM path.
  • If duplicate asset found, should trash previous asset and reimport new asset under new path.

Implementation:

  1. */core/pom.xml — Add dependency
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.resource.filter</artifactId>
</dependency>

2. parent */pom.xml — Add dependency + `conditionalpackage` under binding instructions for bnd-maven-plugin. Newer archtypes write the bindings under separate *.bnd file. Instructions are same.

<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.resource.filter</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
<plugin>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bnd-maven-plugin</artifactId>
<version>${bnd.version}</version>
<executions>
<execution>
<id>bnd-process</id>
<goals>
<goal>bnd-process</goal>
</goals>
<configuration>
<bnd>
<![CDATA[
Bundle-Category: ${componentGroupName}

# export all versioned packages except for conditional ones (https://github.com/bndtools/bnd/issues/3721#issuecomment-579026778)
-exportcontents: ${removeall;${packages;VERSIONED};${packages;CONDITIONAL}}

# reproducible builds (https://github.com/bndtools/bnd/issues/3521)
-noextraheaders: true
-snapshot: SNAPSHOT

# Add Sling Resource Filter under conditionalpackage here.
-conditionalpackage: org.apache.sling.resource.filter.*

Bundle-DocURL:
-plugin org.apache.sling.caconfig.bndplugin.ConfigurationClassScannerPlugin
-plugin org.apache.sling.bnd.models.ModelsScannerPlugin
]]>
</bnd>
</configuration>
</execution>
</executions>

Only change added is `-conditionalpackage: org.apache.sling.resource.filter.*`

3. java code

private Resource findExistingAssetInSameName(Resource damRootFolder, String nodeName) {
return new ResourceStream(damRootFolder)
.stream(folder -> folder.isResourceType(JcrResourceConstants.NT_SLING_FOLDER))
.filter(res -> DamUtil.isAsset(res) && StringUtils.equals(res.getName(), nodeName))
.findFirst()
.orElse(null);
}

Steps to use:

  • Initialize new ResourceStream() from the starting resource. My case it was /content/dam.
  • Define branch selector with .stream() method. This tells stream when to stop collecting resources. Traversal stopper. If condition met, it traverses to child resources. In above example, stream keeps traversing into child resources, if it is sling:Folder
  • Define child selector with .filter() method. This tell the stream to include/exclude current resource. When stream keeps traversing resources, if resource satisfies filter condition, its included in stream. Else stream keeps traversing until last resource.
  • That’s it. Finally you have stream of resources. My usecase was simply to snap at first occurrence. This can expand further to
  1. Iterate through results and consume immediately
new ResourceStream(parentFolder)
.stream(res -> res.isResourceType(JcrResourceConstants.NT_SLING_FOLDER))
.filter(DamUtil::isAsset)
.forEach(this::useAsset);

2. Collect results into another collection and return

return new ResourceStream(parentFolder)
.stream(res -> res.isResourceType(JcrResourceConstants.NT_SLING_FOLDER))
.filter(DamUtil::isAsset)
.collect(Collectors.toList());

Conclusion:

Sling ResourceStreams are much efficient method of tree traversal. ResourceFilterStream helps to combine Predicates with ResourceStream to write more verbose conditions. I removed all queries from my codebase and replaced with ResourceStreams and not running into traversal limit errors anymore.

--

--

Saravana Prakash

AEM Fullstack Enthusiast. Working on AEMCaaS, Adobe EDS, Adobe IO and other Adobe Marketing Cloud tools