Page tree
Skip to end of metadata
Go to start of metadata

Your Rating: Results: 1 Star2 Star3 Star4 Star5 Star 143 rates
It is a truth universally acknowledged that writing complex xpath queries by hand is painful and error-prone. This profound thought crossed my mind when working at a project which involved several different search screens, each with lots of options (that is filters) diversely combined with AND/OR but which ultimately queried a DMS repository. So, on a rainy sunday morning, I thought to myself: If only I could have something like Hibernate's Criteria!

This is the genesis of openutils-mgnlcriteria.

Criteria is a simplified API for retrieving JCR Nodes by composing Criterion objects. This is a very convenient approach for functionality like "search" screens where there is a variable number of conditions to be placed upon the result set.

The JCRCriteriaFactory is a factory for Criteria. Criterion instances are usually obtained via the factory methods on Restrictions.

openutils-mgnlcriteria API is blatantly inspired by Hibernate's Criteria API.

openutils-mgnlcriteria requires JDK 1.5.x or superior

Usage

People already familiar with Hibernate's Criteria will find almost no difference (type names and methods have been kept the same on purpose, whenever possible): you create a Criteria object with one of the static methods in JCRCriteriaFactory and start adding Restrictions and a final optional Order. Then you call the list() method to get your Collection of results (that is instances of info.magnolia.cms.core.Content). As in Hibernate's Criteria, method chaining is supported. Here is an example:

Calendar begin = Calendar.getInstance();
begin.set(2004, Calendar.JANUARY, 1);
Calendar end = Calendar.getInstance();
end.set(2008, Calendar.DECEMBER, 1);

Collection<Content> pets = JCRCriteriaFactory.createMgnlCriteria("//dogs//*", MgnlContext.getQueryManager("website"), "mgnl:content").add(
Restrictions.contains("@name", "Nana")).add(
Restrictions.gt("@weight", Float.valueOf(10))).add(
Restrictions.between("@birthDate", begin, end).addOrder(
Order.desc("@name")).list();

All this will be translated into the following xpath statement

//dogs//*[((jcr:contains(@name, 'Nana')) and (@weight>10.0) and (@birthDate >=xs:dateTime('2004-01-01T00:00:00.000+00:00') and @birthDate <=xs:dateTime('2008-12-01T23:59:59.000+00:00')))] order by @name descending

You can also specify a different type to be returned in the Collection of results. eg.

Collection<Pet> pets = JCRCriteriaFactory.createMgnlCriteria("//dogs//*", MgnlContext.getQueryManager("website"), "mgnl:content", Pet.class).add(
Restrictions.contains("@name", "Nana")).add(
Restrictions.gt("@weight", Float.valueOf(10))).add(
Restrictions.between("@birthDate", begin, end).addOrder(
Order.desc("@name")).list();

Internally, this will use info.magnolia.content2bean.Content2BeanUtil.toBean() to transform nodes into beans.

So, for example, if you have a domain Pet class like this

public class Pet
{
 private String name;
 private Float weight;
 private Calendar birthDate;
 //getters and setters here...
}

Content nodes returned by the above query will be automatically converted to and populate instances of the Pet type.

Furthermore, you may want to have only a subset of the whole resultset returned, much like in a MySQL limit clause. In this case, you will use the JCRCriteriaFactory.createMgnlCriteriaWithLimit factory method. For this to work, the underlying JCR repository implementation must support this feature (Jackrabbit 1.4+ does).

Here is an example.

Collection<Pet> pets = JCRCriteriaFactory.createMgnlCriteriaWithLimit("//dogs//*", MgnlContext.getQueryManager("website"), "mgnl:content", Pet.class).add(
Restrictions.contains("@name", "Nana")).add(
Restrictions.gt("@weight", Float.valueOf(10))).add(
Restrictions.between("@birthDate", begin, end).
setFirstResult(5).
setMaxResults(10).
addOrder(Order.desc("@jcr:score()")).list();

Notice the setFirstResult(int) and setMaxResults(int) methods. Now calling list() will return a subset of only five Pet objects, starting from the 6th item (counting starts from 0). If you dont specify these two calls, list() will behave as usual by returning the entire result set. If you only call setMaxResults(int), the result set will be the subset of elements (0, maxResults) (firstResultValue is 0 by default).

A word of warning about implementations returned by JCRCriteriaFactory. They are NOT thread-safe, therefore client code wishing to use one of them as a shared global variable MUST coordinate access to it. These objects are actually meant to be instantiated and used within a method scope (e.g. a service method), where no concurrent issues arise.

Finally, it is good to know that openutils-mgnlcriteria API catches all checked exceptions thrown by JCR and Magnolia and wraps them into its own runtime net.sourceforge.openutils.mgnlcriteria.jcr.query.JCRQueryException, leaving to the API user the choice whether to catch it or not and, when needed, get to the original cause of error.

You can check out the openutils-mgnlcriteria from sourceforge svn (sorry, no Maven artifacts yet) here https://openutils.svn.sourceforge.net/svnroot/openutils/trunk/openutils-mgnlcriteria or, if you are using Maven, you can include it in your project as a dependency with the following snippet

<dependency>
    <groupId>net.sourceforge.openutils</groupId>
    <artifactId>openutils-mgnlcriteria</artifactId>
    <version>[version here]</version>
</dependency>

See here for the latest artifact version http://mvnrepository.com/artifact/net.sourceforge.openutils/openutils-mgnlcriteria/

7 Comments

  1. Another amazing piece of work! Thank so much. For the record, is this module under the umbrella of Openmind or you personally? You can also change the info in the module listif I guessed wrong... 

    1. Hi Boris, thank you! As to your question, the API is part of OpenMind's OpenUtils project.

  2. Federico, in the modules list I have named this module "Critera Search" - I think that this makes it easier for people to get an idea what it might be about (wink)

    If you like to, feel free to use that name.

  3. Note that JCR2 also provides a query api (I haven't looked at it, but I do hope they've made their job correctly and took note of the best examples around), and that it also deprecates xpath queries ...

    1. Hi Grégory, I must admit that I did not know about JCR2 but from what I've just seen, the jackrabbit implementation of it looks not very mature and the query API doesnt seem to have a Criteria-like interface. Anyway, hopefully I didnt reinvent the wheel (smile) 

      1. Great then; I only knew they had a new query api, but it could be, indeed that there's no criteria-api like this one anyway; just wanted to point it out (smile)
        The API itself should be mature/close to final if not already. The implementation is another story.

  4. Hey, at first glance I hadn't seen that this was using content2bean to get you actual beans, that's great (smile)