This article covers a Bloomreach Experience Manager version 12. There's an updated version available that covers our most recent release.

Find all documents that contain a link to a particular document

Introduction

Goal

Create and execute a search query to find all documents that contain a link to a certain document.

Use Cases

A common use case for an "incoming links query" is retrieving comments on an article. The article as well as the comments are all documents in the content repository, and the comments are linked to the article through a document link. 

Technical Background

In Bloomreach Experience Manager relations between documents are created by linking between documents. At content repository level these links are represented by nodes of type hippo:mirror or hippo:facetselect (or a subtype of one of these).

Take the use case described above of comments on an article. A comment is typically represented by a separate document in the repository. To relate it to the document that it is a comment on, it has a child node of type hippo:mirror that contains in its hippo:docbase property the UUID of the handle node of the document you link to. The content structure might look like this:

/content:
  /documents:
    /news:
      /2012:
        /my-news-article:
          jcr:primaryType: hippo:handle
          jcr:uuid: xxxx-xxxx-xxxx-xxxx
          /my-news-article:
            jcr:primaryType: hippo:document
    /comments:
      /2012:
        /my-news-article:
          jcr:primaryType: hippostd:folder
          /comment-1:
            /hippo:mirror:
              hippo:docbase: xxxx-xxxx-xxxx-xxxx
          /comment-2:
            /hippo:mirror:
              hippo:docbase: xxxx-xxxx-xxxx-xxxx

In the structure above, there is a my-news-article document, including its handle node. The UUID of this handle node has the stable value xxxx-xxxx-xxxx-xxxx. Also there are two comments, comment-1 and comment-2, that both have a hippo:mirror child node containing a hippo:docbase property with value xxxx-xxxx-xxxx-xxxx. In this way the comments are related to my-news-article.

In order to find all comments related to my-new-article one should query for comments that have a hippo:mirror node with a hippo:docbase value equal to my-news-article's UUID (xxxx-xxxx-xxxx-xxxx).

Hippo's delivery tier (HST) API includes a ContentBeanUtils class which provides a number of convenience methods (createIncomingBeansQuery) to create such a query.

Examples

Given the use case above, when you want to render my-news-article in a page, you may also want to show the related comments, for example the first 10 comments, sorted by date, with paging, etc. To do this, you'll need to construct and execute a query. Below are two examples using ContentBeanUtils#getIncomingBeansQuery:

Example 1: Get the 10 Most Recent Comments on a News Article

@Override
public void doBeforeRender(HstRequest request, HstResponse response)
                                              throws HstComponentException {

  final HstRequestContext context = request.getRequestContext();

  // we assume a news detail component, thus expect a NewsDocument
  NewsDocument newsDocument = context.getContentBean(NewsDocument.class);
  if (newsDocument == null) {
     response.setStatus(HstResponse.SC_NOT_FOUND);
     return;
  }

  // set the newsDocument on the request
  request.setAttribute("document", newsDocument);

  // now, also try to find the 10 most recent comments
  try {
   // below, a HstQuery gets bootstrapped that searches for documents of type
   // 'CommentBean' who
   // 1. have a hippo:mirror link stored at a child node 'example:commentlink'
   //    (hence uuid stored in 'example:commentlink/@hippo:docbase')
   // 2. have their hippo:mirror link point to 'newsDocument'
   // 3. are located below getSiteContentBaseBean(request), thus, the entire
   //    content of the (sub)site
   HstQuery commentsQuery = ContentBeanUtils.createIncomingBeansQuery(
                   newsDocument, context.getSiteContentBaseBean(),
                   "example:commentlink/@hippo:docbase",
                   CommentBean.class, false);

   // to the created query, you can do the normal stuff you can do with a
   // HstQuery

   // for example order by date descending
   commentsQuery.addOrderByDescending("example:date");
   // set a limit of 10
   commentsQuery.setLimit(10);

   // execute the search and store the comments HstQueryResult containing
   // 'CommentBean's on the request
   HstQueryResult comments = commentsQuery.execute();
   request.setAttribute("comments", comments);
  } catch (QueryException e) {
      log.warn("QueryException ", e);
  }
}

The rendering template can then iterate through the comments using a HippoBeanIterator obtained through HstQueryResult#getHippoBeans().

Example 2: Additional Constraints on the Comment Query

You can also add extra constraints to the HstQuery that is returned by the static createIncomingBeansQuery method using the Legacy Search API. Because the HstQuery is created using ContentBeanUtils#getIncomingBeansQuery rather than HstQueryBuilder#create, the Fluent Search API can't be used to add extra constraints. 

In the example below, the query is restricted to comments made during the last year, and containing some search query string:

@Override
public void doBeforeRender(HstRequest request, HstResponse response)
                                              throws HstComponentException {

  final HstRequestContext context = request.getRequestContext();

  // we assume a news detail component, thus expect a NewsDocument
  NewsDocument newsDocument = context.getContentBean(NewsDocument.class);
  if(newsDocument == null) {
     response.setStatus(HstResponse.SC_NOT_FOUND);
     return;
  }

  // set the newsDocument on the request
  request.setAttribute("document", newsDocument);

  // now, also try to find the 10 most recent comments
  try {
    // below, a HstQuery gets bootstrapped that searches for documents of type
    // 'CommentBean' who
    // 1 have a hippo:mirror link stored at a child node 'example:commentlink'
    //   (hence uuid stored in 'example:commentlink/@hippo:docbase')
    // 2 have their hippo:mirror link point to 'newsDocument'
    // 3 are located below getSiteContentBaseBean(request), thus, the entire
    //   content of the (sub)site
    HstQuery commentsQuery = ContentBeanUtils.createIncomingBeansQuery(
                        newsDocument, context.getSiteContentBaseBean(),
                        "example:commentlink/@hippo:docbase",
                        CommentBean.class,
                        false);

    // to the created query, you can do the normal stuff you can do with a
    // HstQuery

    // for example order by date descending
    commentsQuery.addOrderByDescending("example:date");
    // set a limit of 10
    commentsQuery.setLimit(10);

    Filter extraFilter = commentsQuery.createFilter();

    // filter for only comments since last year
    Calendar sinceLastYear = Calendar.getInstance();
    sinceLastYear.add(Calendar.YEAR, -1);
    extraFilter.addGreaterOrEqualThan("example:date", sinceLastYear);

    // set the free text search constraint
    String query = SearchInputParsingUtils.parse(..., false);
    extraFilter.addContains(".", query);

    // add the extra filter to the filter of the commentsQuery
    ((Filter) commentsQuery.getFilter()).addAndFilter(extraFilter);

    // execute the search and store the comments HstQueryResult containing
    // 'CommentBean's on the request
    HstQueryResult comments = commentsQuery.execute();
    request.setAttribute("comments", comments);
   } catch (QueryException e) {
      log.warn("QueryException ", e);
   }
}

Again, the rendering template can then iterate through the comments using a HippoBeanIterator obtained through HstQueryResult#getHippoBeans().

Broader Queries Using Wildcards and Depth

The above examples assume that you have a fixed (XPath) location in the CommentBean where the links to the NewsDocument are stored, i.e.  example:commentlink/@hippo:docbase. For CommentBean, this is most likely good enough. But assume that you'd like to query for all instances of  HippoDocument that have a link to your document. Then, the link could be stored in a wide range of (XPath) locations in your HippoDocument. For the case where you do not know the location(s) where the links are stored, ContentBeanUtils supports wildcard link paths such as " */@hippo:docbase" or " */*/@hippo:docbase". If you want to query for multiple possible locations, you can specify a List<String> of possible link paths, for example {"*/@hippo:docbase", "*/*/@hippo:docbase"}. The latter can be simplified by specifying a depth rather than a list of link paths. Here are the link paths the supported values for depth correspond to:

  • int depth = 1 <=> linkPaths = {"*/@hippo:docbase"}

  • int depth = 2 <=> linkPaths = {"*/@hippo:docbase", "*/*/@hippo:docbase"}

  • int depth = 3 <=> linkPaths = {"*/@hippo:docbase", "*/*/@hippo:docbase", "*/*/*/@hippo:docbase"}

  • int depth = 4 <=> linkPaths = {"*/@hippo:docbase", "*/*/@hippo:docbase", "*/*/*/@hippo:docbase", "*/*/*/*/@hippo:docbase"}

Using depth, you can create your incoming beans query like this:

// if we use depth = 2 like below, we find 'incoming beans' that have
// a link at "*/@hippo:docbase" or "*/*/@hippo:docbase"
int depth = 2;
HstQuery commentsQuery = ContentBeanUtils.createIncomingBeansQuery(
                     newsDocument, context.getSiteContentBaseBean(),
                     depth, HippoDocument.class, false);
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?