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 Hippo CMS 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 (handle : uuid = xxxx-xxxx-xxxx-xxxx) | + my-news-article (document) + comments + 2012 + my-news-article (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);