How to use Query Builder in AEM 6.5

Overview of Query Builder with different examples

There are different ways to search content inside of AEM. One of them is via Query Builder.

Query Builder is a server side framework which creates queries based on the set of predicates provided.
The best way to create predicates is using the Query Builder Debugger Tool that you can find at http://localhost:4502/libs/cq/search/content/querydebug.html

If you have installed the we-retail package, you can try with this simple query. The result should be the following:

In the box 1, write you query and click on search to get all the results.
If you are wondering how can you write predicates, I’ll tell you in a moment.

A predicate consists of:

  • A predicates Name –e.g., type, path, property
  • A predicate Property– e.g. value, operation. limit. If not provided, you’ll be mirrored, like for path or property. You can see it in box 3 in the image above
  • A predicate Value – Value of predicate, e.g. the value of a property

Here a visual explanation:

If you have to handle multiple predicates of the same name/type, this is the syntax:

  • Name = <number>_<name>

An example:

type=cq:Page
path=/content/we-retail
1_property=jcr:content/cq:template
1_property.value=/conf/we-retail/settings/wcm/templates/hero-page
2_property=jcr:content/jcr:title
2_property.value=Experience

Ill’ show you now a list of AEM common predicates and some of their properties, but you should have a look at the Adobe doc here to have a complete overview.

  • type: to search a specific JCR node type, both primary node type or mixin type (e.g – cq:Page, dam:Asset)
  • path : to search under within specific path.
    Properties:
    • path.exact=true : If true exact path is matched, if false all descendants are included.
    • path.flat=true : If true searches only the direct children .
    • path.self=true : If true searches the subtree but includes the base node given as path
  • property: to match on JCR properties and their values.
    Properties
    • property.value : the property value to search. Multiple values of a particular property could be given using N_property.value=xxx , where N is number from 1 to N and xxx is the value.
    • N_value: use 1_value2_value, … to check for multiple values (combined with OR by default, with AND if and=true)
    • property.and : set to true for combining multiple values (N_value) ) with AND 
    • property.depth : The number of additional levels to search under a node. e.g. if property.depth=4 then the property is searched till the 4th level from the base node.
    • property.operation : “equals” for exact match (default), “unequals” for inequality comparison, “like” for using the jcr:like xpath function , “not” for no match , (value param will be ignored) or “exists” for existence match .(value can be true – property must exist, the default – or false – same as “not“).
  • nodename: to match on JCR node names.
  • root: it supports all features of a group and allows to set global query parameters.
    The name “root” is never used in a query, it’s implicit.  
    Properties
    • p.offset: number indicating the start of the result page, i.e. how many items to skip
    • p.limit: number indicating the page size
    • p.guessTotal: recommended: avoid calculating the full result total which can be costly; either a number indicating the maximum total to count up to (for example 1000, a number that gives users enough feedback on the rough size and exact numbers for smaller results) or “true” to count only up to the minimum necessary p.offset + p.limit
  • group: it allows to build nested conditions. You’ll see an example later
  • daterange: to match JCR DATE properties against a date/time interval. This uses the ISO8601 format for dates and times (YYYY-MM-DDTHH:mm:ss.SSSZ) and allows also partial representations, like YYYY-MM-DD. Alternatively, the timestamp can be provided
    • daterange.lowerBound : Set a lower bound date range e.g. 2014-09-25
    • daterange.lowerOperation : “>” (default) or “>=”
    • daterange.upperBound: Seta upper bound date range e.g. 2019-12-31
    • daterange.upperOperation: “<” (default) or “<=”
  • relativedaterange: to match JCR DATE properties against a date/time interval using time offsets relative to the current server time. You can specify lowerBound and upperBound using either a millisecond value or the bugzilla syntax 1s 2m 3h 4d 5w 6M 7y (one second, two minutes, three hours, four days, five weeks, six months, seven years). Prefix with “” to indicate a negative offset before the current time. If you only specify lowerBound or upperBound, the other one will default to 0, meaning the current time.
  • tagid: to search for content tagged with one or more tags, by specifying tag IDs. You’ll see an example
  • orderby: it allows to sort the result. If ordering by multiple properties is required, this predicate needs to be added multiple times using the number prefix, such as 1_orderby=first2_oderby=second.
    • orderby: either JCR property name indicated by a leading @, for example @jcr:lastModified or @jcr:content/jcr:title, or another predicate in the query, for example 2_property, on which to sort
    • sort: sort direction, either “desc” for descending or “asc” for ascending (default)
    • case: if set to “ignore” will make sorting case insensitive, meaning “a” comes before “B”; if empty or left out, sorting is case sensitive, meaning “B” comes before “a”

Now that you know how to create a query, let’s transform your predicates in Java. There are more ways to create AEM queries in java, I personally use the hash map as it is simple and really easy.

Example 1: retrieve all the image under a specific path, with a specific mime type.

type=dam:Asset
path=/content/dam/we-retail
property=jcr:content/metadata/dam:MIMEtype
property.value=image/png

What you need to write insider your class is the following piece of code. Remember to inject properly the Query Builder (@Inject in a model, @Reference in a Servlet or OSGi, etc)

      
        Session session= resourceResolver.adaptTo(Session.class);
        Map<String, String> map = new HashMap<>();
        //Type and mime type
        map.put("property", "jcr:content/metadata/dam:MIMEtype");
        map.put("property.value", "image/png");
        map.put("path", "/content/dam/we-retail");
        map.put("type", "dam:Asset");
        map.put("p.limit", "-1");
        PredicateGroup predicateGroup = PredicateGroup.create(map);
        Query query = queryBuilder.createQuery(predicateGroup, session);

        SearchResult result = query.getResult();
        List<Hit> hits = result.getHits();

        for(Hit hit : hits){
            try {
                String path = hit.getPath();
            } catch (RepositoryException e) {
                e.printStackTrace();
            }
        }
        

In each Hit you can get the path of the resource. Based on your need choose how to use them.

Example 2:

Here I’m looking for pages with a specific template and with at least one tag in common. I’m using the “group” predicate to create nested conditions in OR (by default) for each tag available in the current page.

Page homepage = currentPage.getAbsoluteParent(4);    
if(homepage != null){
       Tag[] currentPageTags = currentPage.getTags();
       Session session = resourceResolver.adaptTo(Session.class);
            Map<String, String> queryParameters = new HashMap<>();
            queryParameters.put("path", homepage.getPath());
            queryParameters.put("type", "cq:page");
            queryParameters.put("property", "jcr:content/cq:template");
            queryParameters.put("property.value", "aTemplate");
            queryParameters.put("property.depth", "6");
            if (currentPageTags != null && currentPageTags.length > 0) {
                queryParameters.put("group.p.or", "true");
                for (int i = 0; i < currentPageTags.length; i++) {
                    queryParameters.put("group." + i + "_tagid", currentPageTags[i].getTagID());
                    queryParameters.put("group." + i + "_tagid.property", "jcr:content" + "/" + "cq:tags");
                }
            }
            Query query = queryBuilder.createQuery(PredicateGroup.create(queryParameters), session);
            SearchResult result = query.getResult();
.....
}

In latest AEM versions, most of the time it’s mandatory to create an index for your query, but it’s really simple. Go to this link https://oakutils.appspot.com/generate/index and paste your query to generate it.
I’m used to paste the XPath query, which is automatically generated in the debugger tool in the box 2. If you don’t remember, check it in the first image.

I used the query of the example 1. Here is the output:

There are different types generated (Text, JSON, XML). If you want to create the index directly on AEM (under /oak:index), choose the type which is more readable for you and do it.
Otherwise, you could add it in your project and deploy it automatically.

That’s all! This has been tested on AEM 6.5 but I’m pretty sure that can work on AEM 6.4 and 6.3.

Cheers! 🍔

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: