One of the strong features of Adobe Experience Manager (AEM) is creating UI content using the content authoring capability with Core Components. As contents are created, they are stored in a Jackrabbit Oak repository as resources, which is then fetched and rendered as HTML content. When out-of-the-box content authoring components do not suit the needs of the business, AEM provides features to create custom UI components using the HTL framework and Sling Models.
What is a Resource?
In AEM, everything is stored as structured content in a Jackrabbit Oak Repository. Jackrabbit Oak content information is stored in the form of nodes containing values as attributes. Jackrabbit Oak API refers to these nodes as a Resource. Resources stores the values in attributes. In UI components, developers use Resource and its values to render the UI on the web pages.
For developers to access Resource, AEM provides multiple API’s. Why multiple API’s? Accessing Resource have a cost to the UI component performance, affecting the page load time for non-cached pages. In this blog post, we are going to highlight when to use QueryBuilder, ResourceResolver, and ResourceUtil API.
Query Builder is built on top of JCR API. In AEM, information is stored in a structural format as resource, attributes, and values. Query Builder API is like SQL Query where queries are executed to fetch structural data. Query Builder consists of a statement that includes predicates, expression, and clauses similar to SQL statement.
Use Cases for Query Builder
Query Builder comes in handy when you:
- Fetch large data set to display in UI as List
- Fetch large data set based on given criteria like attribute values
- Fetch data based on text like text search
Query Builder API supports XPath, SQL2 and SQL format statements. SQL is deprecated, so in this blog we are going to talk about XPath and SQL2.
XPath query in simple words is described as syntax to read specific nodes that match given patterns in the predicates. Here, the nodes are the resource path and pattern that will be valued matching attribute values.
path=/content/we-retail/us/en type=cq:Page 1_property=jcr:content/cq:template 1_property.value=/conf/we-retail/settings/wcm/templates/hero-page
In the above example, the path denotes resource path, 1_property denotes attribute, and 1_property.value is attribute value.
Java Code Example:
Map<String, String> predicateMap = new HashMap<>(); predicateMap.put("path", "/content/we-retail/us/en"); predicateMap.put("type", "cq:Page"); predicateMap.put("1_property","jcr:content/cq:template"); predicateMap.put("1_property.value", "/conf/we-retail/settings/wcm/templates/hero-page"); Session session = resourceResolver.adaptTo(Session.class); QueryBuilder queryBuilder = resourceResolver.adaptTo(QueryBuilder.class); Query query = queryBuilder.createQuery(PredicateGroup.create(predicateMap), session); query.setStart(0); SearchResult result = query.getResult();
Note: XPath is deprecated, Apache Jackrabbit Oak still supports XPath usage.
SQL2 Query is similar to RDMS query. Queries are written to fetch column values that match to a given criteria for a specific path.
SELECT * FROM [nt:base] AS s WHERE ISDESCENDANTNODE([/content/we-retail]) AND s.[cq:template] = '/conf/we-retail/settings/wcm/templates/hero-page'
In the above example, * denotes all columns for criteria cq:template equal to the hero-page template under the we-retail content path.
Java Code Examples:
String queryString = "SELECT * FROM [nt:base] WHERE ISDESCENDANTNODE('/content/we-retail/us/en') AND [cq:template] = '/conf/we-retail/settings/wcm/templates/hero-page'"; Iterator<Resource> resourceIterator = resourceResolver.findResources(queryString, Query.JCR_SQL2);
String queryString = "SELECT * FROM [nt:base] WHERE ISDESCENDANTNODE('/content/we-retail/us/en') AND [cq:template] = '/conf/we-retail/settings/wcm/templates/hero-page'"; QueryManager queryManager = session.getWorkspace().getQueryManager(); Query query = queryManager.createQuery(queryString, Query.JCR_SQL2); QueryResult result = query.execute();
Things to know:
- Just like RDBMS queries, Query Builder queries use indexes to fetch the data set.
- AEM provides tools to the Query Explain tool to check the performance of custom queries. http://localhost:4502/libs/granite/operations/content/diagnosistools/queryPerformance.html
- Analyzing and optimizing indexes is important for the optimal performance of the server. If indexes are not created correctly, it will impact the query traversal and the server performance will be impacted. For more information read Oak Queries and Indexing.
- AEM has a limitation for the number of rows that are fetched. Apache Jackrabbit Query Engine Settings Service limits the number of nodes fetched into the memory. If business requirements dictate to fetch large data set, then out-of-the-box (OOTB) settings, we can adjust “In memory read limit.” But, you need to be cautious with hardware sizing which will have an impact on the server’s performance.
- If you’re using text search, it will not return expected results. Indexes need to be optimized and for more information on this, you can read the Increasing Desired Search Results with Oak Indexing Analyzer.
For more information, visit Query Builder API | Adobe Experience Manager.
Another important API is ResourceResolver API provided by Apache Sling. This API provides methods to create resource, fetch specific resource and its children, and find resources. The uniqueness of this API is created using the Sling HTTP Servlet Request or with the Resource Resolver Factory. This is very useful because either current user session or service user-specific permission will be implicitly applied while doing Create, Update, or Delete operations.
Use Cases for Resource Resolver
- Fetching a resource when a path is provided
- Fetching children of a given resource
- CRUD operation in a workflow or batch process
Java Code Example:
Resource resource = resourceResolver.getResource(“/content/we-retail/us/en/men”);
In the above example, resourceResolver object fetches the specific resource called men from the path /content/we-retail/us/en/men and creates an object. For more information, visit ResourceResolver (Apache Sling 11 API).
ResourceUtil is a static class provided by Apache Sling API. This class is very useful when we don’t have to write extra code to create JCR nodes. This class provides methods to create a resource using the existing created Resource Resolver and path and to provide attributes and values as maps. This also helps developers to easily access attribute values quickly.
Java Code Example:
ValueMap properties = ResourceUtil.getValueMap(resource);
In the above example, resource util class returns the property value as ValueMap object. For more information, read ResourceUtil (Apache Sling 11 API).
Value Map can be obtained in multiple ways. If we already have the resource object, then using the adaptTo() method we can get ValueMap as shown below:
ValueMap properties = resource.adaptTo(ValueMap.class);
If we are using the SlingModel, we can use the @ValueMapValue annotation to get current objects property value like this:
@ValueMapValue(name = "jcr:title") private String title;
After this quick read, I hope you will have a broad idea about Resources in AEM and how they fulfill the need to store content. We also went through different API’s for fetching these resources along with the use cases which should be taken into account while choosing an API to get the resources.
Please feel free to share your views and do add your questions in the comment section below.