Using JCR

11 December 2019

A Content Repository API for Java (JCR) is a specification form Java application programming interface (API) to access content repositories. Data in JCR is kept in a tree of nodes with a hierarchical structure.

One of the open source implementations available is Apache Jackrabbit. This implementation is used by multiple Content Management Systems (CMS) like Magnolia, Jahia or Hippo CMS.

Apache Jackrabbit

Apache Jackrabbit is used in multiple CMS, so understanding its flow of data will be helpful in understanding its usage in other systems. The following information and more can be found on this page:  https://jackrabbit.apache.org/jcr/first-hops.html

Dictionary

In Apache JackRabbit there are some key words that are worth knowing:

  • Repository – an area prepared by JackRabbit where data is stored with a set of operations to access\modify it
  • Node – a single unit of a JCR tree stored in a repository can have multiple subnodes (child nodes) and properties
  • Root node – a node without a parent
  • Property – stored parameters of a node
  • Workspace – an area in a repository, helpful when there is need to separate nodes structures from each other
  • Session – a session opened in a chosen repository and workspace after login. Depending on the login, permissions may differ e.g. an anonymous user cannot save a new node.

Installation

One of the easiest ways to install is by adding a few dependencies to a pom file using the Apache Maven management tool.

<dependencies>
    <!-- The JCR API -->
    <dependency>
        <groupId>javax.jcr</groupId>
        <artifactId>jcr</artifactId>
        <version>2.0</version>
    </dependency>
    <!-- Jackrabbit content repository -->
    <dependency>
        <groupId>org.apache.jackrabbit</groupId>
        <artifactId>jackrabbit-core</artifactId>
        <version>2.12.1</version>
    </dependency>
</dependencies>

Adding both dependencies allows the use of JCR functionalities with a basic configuration. It’s enough for basic usage.

Usage

Usage of JCR is mainly focused on two goals – searching data and modifying nodes. There are examples of practical uses of Apache JackRabbit on the following page:

https://jackrabbit.apache.org/jcr/first-hops.html

So as to not copy the whole tutorial, only the key points will be mentioned in following paragraphs.

Login/logout

To gain access to operations on nodes the user needs to be logged in and a session needs to be created. It is possible to log in in the repository for example.:

As an anonymous user to the default workspace:

Session session = repository.login(new GuestCredentials());

As default admin user to the customWorkspace workspace:

Session session = repository.login(new SimpleCredentials("admin", "admin".toCharArray()), "customWorkspace");

The repository can be created on the spot by using the static method JcrUtils.getRepository(), but using JNDI or configuration to create it is better practice.

After finishing, the work session should be closed by:

session.logout()

Searching data

The session allows the user to get the required nodes from the workspace. There are multiple ways of doing that, but most common are through subnodes/parents, by path and by query.

The first one is used by obtaining a parent node or subnodes of a given node and using them.

Node parent = givenNode.getParent();
NodeIterator subNodes = givenNode.getNodes();

The second one is simply by using the getNode method from a node already found, eg:

Node rootNode = session.getRootNode();
Node searchedNode = rootNode.getNode("node1/subNode/searchedNode");

The last one is more complex, by using a query:

String query = „select * from [primitive_type]”; QueryResult qr = session.getWorkspace().getQueryManager() .createQuery(query, "JCR-SQL2").execute(); NodeIterator searchedNodes = qr.getNodes();

Firstly it’s necessary to get queryManager from the workspace, to create the query and then execute it.

The default language used is JCR-SQL2. It allows the creation of complex queries like adding conditions. The result of the query is returned in the form of an iterator.

After obtaining the required node, the easiest way to get its properties is by using the getProperty() method. It enables retrieval of an instance of property class. Getting the value is done by using a specific method like getString()

Property propertyInstance = rootNode.getProperty("propertyName");
String propertyValue = propertyInstance.getString();

Read also: Benefits of immutability in a software development

Changing nodes

A session is needed to operate on nodes, but to modify them there is an additional need for permission. Methods are quite intuitive like:

node.addNode("nodeName");
node.remove();
node.setProperty("propertyName", "propertyValue");
node.setProperty("propertyName", (Value)null);
node.getProperty("propertyName").remove();

The last two are to remove the property from the node. That can be trickier.

The most important part of editing the JCR tree is saving modifications made in session. It is done by using

session.save()

Without that, the method status of the JCR tree will not change.

JCR in Magnolia

A good example of a Content Management System using the JCR mechanism is Magnolia CMS. It is a system which allows users to easily configure the frontend and backend part of an application. JCR nodes are skillfully used to keep both configuration and content readable for developers and non-technical users, making it easy to develop.

Magnolia keeps the JCR Session inside a Magnolia context (MgnlContext class), hides the login/logout mechanism from direct access and hides pure Apache JackRabbit methods in a prepared set of classes for easier use. It is possible to avoid using them, but it is worth knowing about them.

Prepared classes

Most of the functions in Apache JackRabbit used to obtain information from the node tree use RepositoryException. To avoid  handling in Magnolia (info.magnolia.magnolia-core:6.0) there is set of classes with methods which take care of it.

They are named with the JCR object name and the Util part, such as:

  • NodeUtil
  • PropertyUtil
  • SessionUtil

What’s more, these classes give the user additional, sometimes more complex, functionalities e.g., NodeUtil with the method getSiblingsBefore allows the extraction of sibling nodes that are before the chosen node in the iterator of all siblings.

Queries

In a situation when there is a need to find a node of a specific type and there is no node given with a link to that node, like parent-child, there is the possibility of searching via query. In Magnolia there is already a prepared class QueryUtil which facilitates the finding of required nodes by multiple search method implementation. In the most basic one only the workspace name and query statement are necessary to get nodes in the form of an iterator. The default query language is JCR-SQL2.

Testing

In Magnolia there is a prepared set of classes that helps testing. It is possible to add them by using a dependency:

<dependency>
  <groupId>info.magnolia</groupId>
  <artifactId>magnolia-core</artifactId>
  <version>{Version}</version>
  <type>test-jar</type>
  <scope>test</scope>
</dependency>

They help to focus on creating only objects needed for the test and disregarding context, users etc. They are named with a Mock part and JCR name such as:

  • MockNode
  • MockProperty
  • MockSession
  • MockWorkspace
  • MockQuery

An example test method:

@Test
public void should_get_type() {
  //given
  MockNode node = new MockNode(name, "pageType");
  TestedClass testedClass = new TestedClass();
  //when
  String type = testedClass.getType(node);
  //then
  Assertions.assertEquals("pageType", s);
}

Moreover, there are classes to help quickly create a JCR structure like the method createSubnodes of the NodeTestUtill class which allows the loading of structure from file. An example of usage:

MockNode page = new MockNode(name, "mgnl:page");
NodeTestUtil.createSubnodes(page, getClass().getResourceAsStream("/model/page.properties"));
return page;

with page.properties:

/form.@type = mgnl:area
/form/0.@type = mgnl:component
/form/0.mgnl\:template = customTemplate
/form/0/processors.@type = mgnl:contentNode
/form/0/processors/0.@type = mgnl:component
/form/0/processors/0.processorType = processor1
/form/0/fieldsets.@type = mgnl:area
/form/0/fieldsets/0.@type = mgnl:component
/form/0/fieldsets/0/fields.@type = mgnl:area
/form/0/fieldsets/1.@type = mgnl:component
/form/0/fieldsets/1/fields.@type = mgnl:area

More information about testing can be found in documentation on the following page: https://academy.magnolia-cms.com/display/DEV/Unit+tests

Read also: How to Develop an App: Understanding the Development Process

Cookies Policy

Our website uses cookies. You can change the rules for their use or block cookies in the settings of your browser. More information can be found in the Privacy Policy. By continuing to use the website, you agree to the use of cookies.
https://www.efigence.com/privacy-policy/