Bootstrap a Query using the Fluent Search API - BloomReach Experience - Open Source CMS
07-01-2019

Bootstrap a Query using the Fluent Search API

This feature is available since Hippo CMS 11.1.0.

Introduction

Goal

Bootstrap a search query using the Fluent Query API in Bloomreach Experience Manager's delivery tier.

Background

The Fluent Search API in Bloomreach Experience Manager's delivery tier (HST) provides an intuitive way to construct search queries. This page explains how to bootstrap a query using HstQueryBuilder.

Create an HstQuery using HstQueryBuilder

The HstQueryBuilder can create an HstQuery instance from the current request context automatically.

In either HST components or JAX-RS Service Resources, you can apply the same practice:

  1. In an HST component,

    public class LatestItems extends BaseComponent {
        @Override
        public void doBeforeRender(HstRequest request, HstResponse response) {
            final HippoBean scope = RequestContextProvider.get().getSiteContentBaseBean();
            final HstQuery hstQuery = HstQueryBuilder.create(scope)
                    .ofTypes(BaseDocument.class)
                    .build();
            // ...
        }
    }
  2. In a JAXRS Service Resource,

    ProductPlainResource extending AbstractResource:

    @Path("/products/")
    public class ProductPlainResource extends AbstractResource {
        @GET
        @Path("/{productType}/")
        public List<ProductRepresentation> getProductResources(....) {
            final HippoBean scope = RequestContextProvider.get().getSiteContentBaseBean();
            final HstQuery hstQuery = HstQueryBuilder.create(scope)
                    .ofTypes(BaseDocument.class)
                    .build();
            // ...
        }
    }

In cases where you don't have a HstRequestContext (for example for during some background process or for external application integration that does not involve an HTTP request), you can get hold of the HstQueryManager to build an HstQuery with it (through HstQueryBuilder#build(HstQueryManager) method:

Repository repo = HstServices.getComponentManager().getComponent(Repository.class.getName());
Credentials creds = HstServices.getComponentManager().getComponent(Credentials.class.getName() + ".default");
ContentBeansTool cbt = HstServices.getComponentManager().getComponent(ContentBeansTool.class.getName());

Session session = null;

try {
    session = repo.login(creds);
    HstQueryManager queryManager = cbt.createQueryManager(session);

    final Node scope = JcrUtils.getNodeIfExists("/content/documents/myproject", session);
    final HstQuery hstQuery = HstQueryBuilder.create(scope)
            .ofTypes("myproject:news")
            .build(queryManager);
    // ...
} finally {
    if (session != null) session.logout();
}

Scope the Query with HstQueryBuilder

An HstQuery can search content in one or multiple scopes. The scope(s) must be set through HstQueryBuilder#create(HippoBean ... scopeBeans) or HstQueryBuilder#create(Node ... scopeNodes)method. As you can see, it is a variable argument (varargs). So you can use a single scope or multiple scopes.

For example,

final HippoBean base = RequestContextProvider.get().getSiteContentBaseBean();
final HippoBean newsFolder = base.getBean("news");
final HippoBean eventsFolder = base.getBean("events");
// Set multiple search scopes including news/ and events/ folders.
final HstQuery hstQuery = HstQueryBuilder.create(newsFolder, eventsFolder)
        .ofTypes(BaseDocument.class)
        .build();

or

final Session session = RequestContextProvider.get().getSession();
final Node newsFolderNode = JcrUtils.getNodeIfExists("/content/documents/myproject/news", session);
final Node eventsFolderNode = JcrUtils.getNodeIfExists("/content/documents/myproject/events", session);
// Set multiple search scopes including news/ and events/ folders.
final HstQuery hstQuery = HstQueryBuilder.create(newsFolderNode, eventsFolderNode)
        .ofTypes(BaseDocument.class)
        .build();

Please note that HstQueryBuilder#create(...) requires at least one non-null argument. Otherwise, it will throw a RuntimeQueryException due to the illegal scope argument(s).

You can also exclude some scopes in the query execution through HstQueryBuilder#excludeScopes(HippoBean ... excludeScopeBeans) or HstQueryBuilder#excludeScopes(Node ... excludeScopeNodes). These methods also allow variable arguments (varargs). So you can set a single exclusion scope or multiple exclusion scopes.

For example,

final HippoBean base = RequestContextProvider.get().getSiteContentBaseBean();
final HippoBean eventsFolder = base.getBean("events");
// Set a search scope and exclude events/ folder.
final HstQuery hstQuery = HstQueryBuilder.create(base)
        .excludeScopes(eventsFolder)
        .ofTypes(BaseDocument.class)
        .build();

or

final Session session = RequestContextProvider.get().getSession();
final Node baseNode = JcrUtils.getNodeIfExists("/content/documents/myproject", session);
final Node eventsFolderNode = JcrUtils.getNodeIfExists("/content/documents/myproject/events", session);
// Set a search scope and exclude events/ folder.
final HstQuery hstQuery = HstQueryBuilder.create(baseNode)
        .excludeScopes(eventsFolderNode)
        .ofTypes(BaseDocument.class)
        .build();

Please note that if you add a bean or node in both scopes (through #create(...) and excludeScopes (through #excludeScopes(...)), then the excludeScopes will take the precedence over scopes. Any of excludeScopes will be excluded in the search query execution as a result.

Set a Limit and Offset

In general, it is always best to set a limit. If you do not specify a limit, the HST by default will set a limit of 1000. The best way is to set the limit equal to the number of items you want to show, for example pageSize. If you combine this with offset, you can easily always fetch the correct items.

For example, if pageSize = 10, and you want to show the 3rd page ( pageIndex = 3), use:

final HippoBean base = RequestContextProvider.get().getSiteContentBaseBean();
final HstQuery hstQuery = HstQueryBuilder.create(base)
        .ofTypes(BaseDocument.class)
        .limit(pageSize)
        .offset(pageSize * (pageIndex - 1))
        .build();

Now, there is one more catch. If you have set a limit of, say, 10, then, HstQueryResult#getSize() will return at most 10. So, what if you need to know how many pages there are? For this, you can use getTotalSize(). This method returns the total number of hits.

Always set a limit, preferably to just the number you need to show

If you don't set a limit, HST sets a limit of 1000 for safety. This is already large, and might result in slower searches. For optimal performance, set a limit (and optionally an offset).

Sort Results with HstQueryBuilder

The HstQuery can "order by" / "sort by" multiple properties, ascending or descending. The sorting is done according to the order in which the properties to sort on are added. Sorting can only be done on properties (meta-data) which are directly stored on the document. Properties which are part of a Compound in a document can not be used for sorting.

For example:

Sorting / Ordering:

final HippoBean scope = RequestContextProvider.get().getSiteContentBaseBean();
final HstQuery hstQuery = HstQueryBuilder.create(scope)
        .ofTypes(BaseDocument.class)
        // first sort the results on the "example:date" descending (newest first)
        .orderByDescending("example:date")
        // if there are hits with the same "example:date", we then sort ascending by 
        // "example:title" case insensitive
        .orderByAscendingCaseInsensitive("example:title")
        // if there are hits with the same date and title, we sort descending by 
        // "hippostdpubwf:publicationDate"
        .orderByDescending("hippostdpubwf:publicationDate")
        .build()
// ...

Please note that HstQueryBuilder#orderByDescending(String... fieldNames), HstQueryBuilder#orderByDescendingCaseInsensitive(String... fieldNames), HstQueryBuilder#orderByAscending(String... fieldNames) and HstQueryBuilder#orderByAscendingCaseInsensitive(String... fieldNames) are defined with variable arguments (varargs) as well. If you put multiple field names with those methods, it should be equivalent to multiple invocations of the method with each field name argument.

Sorting can only be done on properties directly on the documents

See Sorting search results in a query can only be done on direct properties of Documents.

 

Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?