March 23, 2020
Estimated Post Reading Time ~

Apache Sling Resource filter – Alternate to Jcr Queries

Default AEM Query Engine works lazy, which means it doesn’t load all results directly when doing the request.1000 queries per request will never perform well if when rendering a product page he wanted to references the assets associated with it. This speaks of how expensive JCR Queries could instead be replaced with targeted content structure traversals to allow finding relevant content without searching the whole AEM repository. Unfortunately, from a developer perspective, this means writing a fair amount of boilerplate code to traverse the AEM repository and then identify the relevant Resources.

Fortunately, a new API was just released by the Apache Sling team. This API adds support for using Lambda expressions to filter a stream of Resources from the Sling repository.

This new Resource Filter API allows AEM / Sling developers to be significantly more succinct and readable and how they perform common repository traversals. It’s not a pure replacement of JCR queries or the traditional method of traversing the repository, but for simple content structures, you can do a lot more with a lot less code.

Resource Filter Example

Here’s an example of how one could extract a stream of resources by a page’s component type under a cq:Page:

log.info("Filtering by type: {}", type);
ResourceFilterStream resourceStream = resource.adaptTo(ResourceFilterStream.class);
resourceStream.setBranchSelector("[jcr:primaryType] == 'cq:Page'")
  .setChildSelector("[jcr:content/sling:resourceType] == $type")
  .addParam("type", type).stream()
  .forEach(r -> {
    log.info(r.getPath());
  });

The equivalent pre-Stream code is quite a bit more verbose, even in this simple example:

log.info("Filtering by type: {}", type);
public void traverseChildren(Resource parent){
  for(Resource child : parent.getChildren()){
    if("cq:Page".equals(child.getResourceType())) {
      Resource content = child.getChild("jcr:content");
      if(content != null && type.equals(content.getValueMap("sling:resourceType","")){
        log.info(r.getPath());
      }
      traverseChildren(child);
    }
  }
}
[...]

traverseChildren(resource);
In this example, we are just writing the resource to a JSON Array using the javax.json API, but once you have a stream of Resources, you can use any Lambda function to then process or further filter the stream.

Adding the Resource Filter API in your Project

Adding the Resource Filter API into your project takes two steps. First, add the dependency into your project’s POM:

<dependency>
    <groupid>org.apache.sling</groupid>
    <artifactid>org.apache.sling.resource.filter</artifactid>
    <version>1.0.0</version>
    <scope>provided</scope>
</dependency>

More interesting things are Resource Predicate Service, that allows you to convert a string that defines simple matching requirements into aPredicate<Resource>for use with the Collections and the Streams Java API. In addition, it also allows you to add parameters to the underlying context that the script will use.
<span class="pl-k">@Reference</span>
<span class="pl-smi">ResourcePredicates</span> rp;

<span class="pl-k">Predicate&lt;<span class="pl-smi">Resource</span>&gt;</span> predicate <span class="pl-k">=</span> rp<span class="pl-k">.</span>parse(<span class="pl-s"><span class="pl-pds">"</span>[jcr:content/created] &lt; 2013-08-08T16:32<span class="pl-pds">"</span></span>);
resourceCollection<span class="pl-k">.</span>stream()<span class="pl-k">.</span>filter(predicate)<span class="pl-k">.</span>forEach(
    resource <span class="pl-k">-</span><span class="pl-k">&gt;</span> <span class="pl-smi">System</span><span class="pl-k">.</span>out<span class="pl-k">.</span>println(resource<span class="pl-k">.</span>getPath()));

Resource Stream

ResourceStreamis a general utility to provide aStream<Resource>which traverses a resource and it’s subtree. The implementation takes aPredicate<Resource>object as part of the stream creation to define a branch selector that controls which children of a resource are followed.
In addition, there is agetChildren(Predicate)the method which returns a filtered list of children of the given resource.

Resource Filter Stream

ResourceFilterStreamcombines theResourceStreamfunctionality with theResourcePredicatesservice to provide an ability to define aStream<Resource>that follows specific child pages and looks for specific Resources as defined by the resources filter script. The ResourceStreamFilter is access by adaption.
<span class="pl-smi">ResourceFilterStream</span> rfs <span class="pl-k">=</span> resource<span class="pl-k">.</span>adaptTo(<span class="pl-smi">ResourceFilterStream</span><span class="pl-k">.</span>class);
    
rfs
.setBranchSelector(<span class="pl-s"><span class="pl-pds">"</span>[jcr:primaryType] == 'cq:Page'<span class="pl-pds">"</span></span>)
.setChildSelector(<span class="pl-s"><span class="pl-pds">"</span>[jcr:content/sling:resourceType] != 'apps/components/page/folder'<span class="pl-pds">"</span></span>)
.stream()
.collect(<span class="pl-smi">Collections</span><span class="pl-k">.</span>toList());

Similar to indexing in a query there are strategies that you can do within a tree traversal so that traversals can be done in an efficient manner across a large number of resources. The following strategies will assist in traversal optimization.

Limit traversal paths

In a naive implementation of a tree traversal, the traversal occurs across all nodes in the tree regardless of the ability of the tree structure to support the nodes that are being looked for. An example of this is a tree of Page resources that have have a child node of jcr:content which contains a subtree of data to define the page structure. If the jcr:content node is not capable of having a child resource of type Page and the goal of the traversal is to identify Page resources that match specific criteria then the traversal of the jcr:content node can not lead to additional matches. Using this knowledge of the resource structure, you can improve performance by adding a branch selector that prevents the traversal from proceeding down a non-productive path

Limit memory consumption

The instantiation of a Resource object from the underlying ResourceResolver is a nontrivial consumption of memory. When the focus of a tree traversal is obtaining information from thousands of Resources, an effective method is to extract the information as part of the stream processing or utilizing the forEach method of the ResourceStream object which allows the resource to be garbage collected in an efficient manner.



By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.