Manipulating content with APIs

November 14, 2023

JCR API

The main thing to understand is that in the JCR, everything happens in the context of a session (think of something like an hibernate session in case you are familiar with this framework). In the context of this session, you can create/read/update/delete any content your session can access.

A session is opened by one user, on one workspace, in one language. This means that at anytime in the context of your session, you can only access content allowed to this user, that exists in this workspace (default or live) in this particular language (if no language is provided, you can only read non-internationalized properties) but you can still access the nodes.

So whenever you want to interact with the JCR, the first step is to get a session.

If you are in an Action, then you receive this session as a parameter :

public ActionResult doExecute(HttpServletRequest req, RenderContext renderContext, Resource resource, JCRSessionWrapper session,
                              Map<String, List<String>> parameters, URLResolver urlResolver)

Otherwise, if you do not get the session directly, you can get it from a node (node.getSesion()).

Another way of getting a session if you develop some services is to make SPRING inject the JCRTemplate class in your service.

Once you have a session you can manipulate your content. Remember that when creating a node, you need to first read its parent, then add the new node under this parent, set the properties on this child and then save the session.

Every time you change a node/property remember to save the session before ending your process.

An example of code using the JCRTemplate (this one is injected by SPRING)

private JCRTemplate jcrTemplate;
public void setJcrTemplate(JCRTemplate jcrTemplate) {
    this.jcrTemplate = jcrTemplate;
}

public void doSomething() {
    jcrTemplate.doExecuteWithSystemSession(null,Constants.EDIT_WORKSPACE, Locale.ENGLISH,new JCRCallback() {
        public Object doInJCR(JCRSessionWrapper session) throws RepositoryException {
            JCRNodeWrapper node = session.getNode("/sites/mySite/contents/news");
            String nodeTitle = "My node"
            JCRNodeWrapper jcrNodeWrapper = node.addNode(nodeTitle, "jnt:news");
            jcrNodeWrapper.setProperty("jcr:title", nodeTitle);
            jcrNodeWrapper.setProperty("desc", "my node content");
            session.save();
            return null;
        }
    });
}

jcrTemplate.doExecuteWithSystemSession(null,Constants.EDIT_WORKSPACE, Locale.ENGLISH,new JCRCallback() {

This line indicate that we want to execute a certain callback using a system session (no user in particular, but JCRTemplate allows to use any type of session), in the EDIT_WORKSPACE and in English.

JCRNodeWrapper node = session.getNode("/sites/mySite/contents/news");

This read what will be our root node for this process.

JCRNodeWrapper jcrNodeWrapper = node.addNode(nodeTitle, "jnt:news");

We add a node under our root node, this node will be of type "jnt:news". As long as we do not save our session this node only exist in our context, nobody knows about it. This create an empty shell for our node, now we have to set some properties on it. This is done by calling setProperty methods on our newly created node.

session.save();

This save the session and propagate our node to the JCR definitively where it can be read by others from now on.

To create a node call the add method on its parent, so the steps are :

  • Open the session
  • Get the parent node
  • Call the add method on the parent (do not forget to specify your nodetype)
  • Call setProperty on each properties we want to add on our new node
  • Save the session

To delete a node call the remove method on this node, so the steps are :

  • Open the session
  • Get the node we want to delete
  • Call remove method on it
  • Save the session

To Update a property on an existing node :

  • Open the session
  • Get the node we want to update
  • Call setProperty on each properties we want to update
  • Save the session

Legacy REST API


Jahia provides a REST API, mostly for rendering, but some other services are available as well such: login, logout, content creation & updating and searching.

Note please, when using the REST API from your client-side code (browser side) in your external Web application, e.g. using jQuery library, you can run into a cross-domain restriction for Ajax requests as they are not allowed by default due to security concerns. In contrary, using REST API from a server side code in your external application does not have this restriction.

LOGIN

URL : /cms/login

Method : POST

Parameters :

Name Mandatory Description
username yes The username of the user that you want to login
password yes The user's password
redirectActive no If set to "true" or not set, the login servlet will redirect to the home page after successful authentication. If you are using AJAX calls you should set this to "false".
redirect no Allows you to specify another URL to redirect after successful login
restMode no Set this to true if you are performing a login from an AJAX call login
 

LOGOUT

URL : /cms/logout

Method : POST

Name Mandatory Description
redirectActive no If set to "true" or not set, the login servlet will redirect to the home page after successful authentication. If you are using AJAX calls you should set this to "false".
redirect no Allows you to specify another URL to redirect after successful login

RENDER

The render servlet can either be used to render content by accessing it with a GET method, or can also be used to add content to different workspaces by using a POST method and special parameters. You can also delete content using DELETE method, or update content using POST or PUT.

HTTP Get Request

You can simply render a node by calling it in the following form :

URL : /cms/render/WORKSPACE/LANGUAGE/CONTENT_PATH(.VIEW).TEMPLATETYPE

where: * WORKSPACE : 'default' for the edit workspace, 'live' for the live (production) workspace * LANGUAGE : any language defined on the site * CONTENT_PATH : the full path to the content object to be rendered * VIEW : this is optional, specified the (alternative) view to render the content node. This depends on the available views for the content node. * TEMPLATE-TYPE : usually 'html' but you might want to use other template types such as 'json' (see more below)

JSON rendering

Jahia comes with a few default views for JSON rendering, but here we will detail the two most important ones : 'full' and 'nodetype'. The 'full' view will generate a JSON view of a content node, along with all it's properties and child node names. It also takes optional query string parameters to expand the output:

depthLimit : (default value = 1). The depth of children to output with the current node. You can use this value to output deeper tree of children, but be aware that this will make your HTTP requests slower as more data will be output and transmitted over the network.
escapeColon : (default value = false) If activated, this will replace the colon (':') character present in property names with an underscore, making it a lot easier to use Javascript code that accesses properties.
withType : (default value = false) If activated, it will, along with the property data and child nodes, also output the node type information for the node. As this will generate a lot more data for this request, it is also possible to use the 'nodetype' view to generate this output seperately. Also, the 'nodetype' view also takes parameters that can be directly called on the 'full' view but we will not repeat them here for the sake of brievety (see below for the query string parameters and their documentation).
prettyPrint : (default value = false) By default the JSON rendering will be as compact as possible, which makes it difficult to read by a human, by enabling this setting it will render a nicely indented output.

Full JSON View example query:

http://localhost:8080/cms/wise/default/en/sites/docspaces.full.json?withType=true&depthLimit=0&escapeColon=true&withHidden=true&withProtected=true

Nodetype view

The 'nodetype' view will generate a JSON output of the node type's information, making it possible for example to dynamically generate forms to create a new node. By default it will not output any hidden or protected properties or child node definitions, but this may be overridden with the following query string parameters:

  • withHidden : (default value = false) If activated, it will also output the hidden properties and child node definitions for the node
  • withProtected : (default value = false) If activated, it will also output the protected properties and child node definitions for the node
  • depthLimit : (default value = 1) By default the JSON view will render the current node's nodetype, along with the child definitiond and their nodetypes, but you can change the depth limit to either 0 to output only the nodetype of the current node, or to a higher value to recursively output the types of the children of children, and so on. Of course the deeper the depth and the larger the result tree, so be careful not setting this value too high if not really needed.
  • nodeType : (default value = none) By default the rendered node type (and mixin types) will be of the node specified in the path, but it might be interesting to be able to query any node type registered in the system. You can do this by specified this query parameter, in which case the node type and mixin types of the node specified in the path will simply be ignored and the node type generated will be the one specified in the query string parameter instead. Be careful with the encoding of the parameter, you will have to URL encode the colon (':') character with it's URL equivalent, so for example :
    nt:base

    will become

    nt%3Abase

     

  • prettyPrint : (default value = false) By default the JSON rendering will be as compact as possible, which makes it difficult to read by a human, by enabling this setting it will render a nicely indented output.

Full JSON View examples query

Basic example requesting the node type of the 'docspaces' node including all the hidden and protected properties and child definitions:

http://localhost:8080/cms/wise/default/en/sites/docspaces.nodetype.json?withHidden=true&withProtected=true

Requesting a specific nodetype, ignoring the 'docspaces' node type and mixin types:

http://localhost:8080/cms/wise/default/en/sites/docspaces.nodetype.json?nodeType=nt%3Abase

HTTP Post Request

URL : /cms/render/WORKSPACE/LANGUAGE/CONTENT_PATH

Method : POST

Request headers : see response format section below.

Name Mandatory Description
jcrNodeType yes Define the type of node you want to create.
jcrNodeName no If defined we will create a node with this name (or use it as a prefix for the name. If not define you can call : /cms/render/WORKSPACE/LANGUAGE/CONTENT_PATH/* To create a subnode at the specified path using the nodetype for name.
jcrNewNodeOutputFormat no Define at which format the newly created node will be returned, after the form submission (non-AJAX) is performed and a redirect is sent back to the browser. This value is used as a file extension at the end of the URL. The "html" value is the default value, but "json" or any other view type you have defined in your templates is possible.
jcrReturnContentType no If you are expecting a JSON result to the REST call, you need to either set the "accept" header to "application/json" or use this parameter to force the generation of JSON output. The value must either be "json" to force JSON output or "" (or remove the parameter completely to default to "accept" header checks. This parameter is useful when using framework such as JQuery that send "accept" headers with a "*/*" value to match all types. In this case this parameter implies that we want a JSON response output. The difference between this parameter and the jcrNewNodeOutputFormat one is that this one is used to generate a direct HTTP response whereas the latter is used to generate a redirection URL.
jcrReturnContentTypeOverride no When the jcrReturnContentType is activated, it will default to the following response content type: application/json; charset=UTF-8 This parameter makes it possible to override the response content type to anything you need. Note that if you don't include a charset directive it will automatically append a UTF-8 charset.
jcrRedirectTo no If defined will redirect to the specified URL instead of the newly created node.
jcrMethodToCall no For HTML form to override the method called, this allow to delete content with a simple POST form instead of send a DELETE HTTP request
jcrAutoCheckin no Allow to force versioning of the newly created content.
jcrTargetDirectory no If you have multipart form, this will be the location where the content is uploaded
jcrVersion no If "true" activate the versionning on the posted content (works only for files)
jcr:mixinTypes no Allow you to set a mixin on the new node

AJAX REQUESTS

All AJAX JSON requests should have the following headers set :

accept: application/json x-requested-with : XMLHttpRequest

The second header should be set for you when accessing the API through a browser, but if you are using the REST API through an HttpClient call or even another external client software (such as a mobile device for example), be sure that both these headers are set, otherwise it will default to HTML feedback.

File uploads

In the case of a file upload, when using AJAX requests (by setting the headers as described in the previous section), you actually have to perform two requests. The first request will be a multipart form upload and will save either in the current users' "files" directory, or if the "targetDirectory" property was set it will store the uploaded files there. The response is a JSON object with a "uuids" array property that contains string of the identifiers of all the created file nodes in the repository.

The second request can then use the UUIDs returned by the first AJAX request to set references to the new file nodes.

If a file already exists at the upload target, it will be updated, and if versioning is activated (see the version parameter above), it will create a new version before updating the file.

Response format

In the case of a regular POST method, the default response format is HTML. This format may be changed to JSON by setting up the appropriate request headers (see below).

In the case of HTML we will render the newly created or updated node with its default template (unless you have specified a different template using the newNodeOutputFormat parameter).

In order to change the response format to JSON, you must set the following headers : "accept" = "application/json" and "x-requested-with" = "XMLHttpRequest".

Examples

Post Example

<form action="<c:url value='${url.base}${currentNode.path}/*'/>" method="post">
    <input type="hidden" name="jcrNodeType" value="jnt:post"/>
    <input type="hidden" name="jcrRedirectTo" value="<c:url value='${url.base}${renderContext.mainResource.node.path}'/>"/>
    <%-- Define the output format for the newly created node by default html or by jcrRedirectTo--%>
    <input type="hidden" name="jcrNewNodeOutputFormat" value="html"/>
    <fieldset>
        <p class="field">
            <input value="<c:if test="${not empty currentNode.children}"> Re:</c:if>${currentNode.propertiesAsString['threadSubject']}" type="text" size="35" id="forum_site" name="jcr:title"
                   tabindex="1"/>
        </p>

        <p class="field">
            <textarea rows="7" cols="35" id="jahia-forum-thread-${currentNode.UUID}" name="content" tabindex="2"></textarea>
        </p>

        <p class="forum_button">
            <input type="reset" value="Reset" class="button" tabindex="3"/>

            <input type="submit" value="Submit" class="button" tabindex="4"/>
        </p>
    </fieldset>
</form>

This will create a new comments in a thread of a forum. And after submission we want to redisplay the curren thread, this is why we used the jcrRedirectTo parameter.

Delete Example

<form action="<c:url value='${url.base}${currentNode.path}'/>" method="post"
      id="jahia-forum-post-delete-${currentNode.UUID}">
    <input type="hidden" name="jcrRedirectTo" value="<c:url value='${url.base}${renderContext.mainResource.node.path}'/>"/>
    <%-- Define the output format for the newly created node by default html or by jcrRedirectTo--%>
    <input type="hidden" name="jcrNewNodeOutputFormat" value="html"/>
    <input type="hidden" name="jcrMethodToCall" value="delete"/>
</form>

<a title="<fmt:message key='delete.post'/>" href="#"
   onclick="document.getElementById('jahia-forum-post-delete-${currentNode.UUID}').submit();"><span><fmt:message key="delete.post"/></span>

This example shows how to write a simple form to delete a content with a simple link.

Find

The find servlet can be used to query content inside the Jahia JCR repository. It provides a lightweight but very powerful interface to retrieve content anywhere inside the content repository. Please note that if you need to search for users or groups, you should use the findPrincipal servlet instead, since it integrates with external principal repositories such as LDAP.

Since DX 7.1, this API is disabled by default. You can re-enable it by changing the value of jahia.find.disabled, but it is advised for security reasons to keep it disabled and to use new RESTful API and the prepared queries.

URL : /cms/find/WORKSPACE/LANGUAGE

Method : POST or GET

Parameters :

Name Mandatory Description
query yes The JCR query to execute. You can reference request parameters in your query by using the following syntax : $paramName which will look for a request parameter called "paramName"
language no The JCR language used in the query, for example "JCR-SQL2" or "xpath" Defaults to "JCR-SQL2"
limit no The maximum number of results to return. Default set to 20 with a maximum of 100. Use the offset parameter to retrieve more than 100 results.
offset no The 0-index based offset to start results with. Defaults to 0.
depthLimit no The depth of the result tree. If not set it defaults to "1". If you have deep content tree hierarchies you might want to limit result depth to avoid performance issues.
escapeColon no If set to true, the ':' in the node property name will be replaced '_'. Defaults to false.
propertyMatchRexexp no The value you specify here will be used to match properties in the result set, and will build a matchingProperties JSON list that will let you known which properties have matched. This is especially useful when using fulltext search, since the JCR doesn't indicate which property has matched the criteria. The value you specify must comply with the JDK Pattern Regexp syntax.
removeDuplicatePropValues no If activated, it will filter the matched properties (that were matched using the propertyMatchRegexp above parameter) to make sure only one node per property value is returned. This is useful when building unique list of property values for auto-completion. Defaults to false.

Example

HTTP GET Request (formatted for legibility) :

http://localhost:8080/cms/find/default/en
?q=t
&limit=10
&query=%2Fjcr%3Aroot%2Fsites%2FmySite%2Ftags%2F%2Felement(*%2C+jnt%3Atag)%5Bjcr%3Acontains(.%2C'%7B%24q%7D*')%5D%2F%40j%3Anodename
&language=xpath
&escapeColon=false
&propertyMatchRegexp=%7B%24q%7D.*
&removeDuplicatePropValues=false

In the above example, the values are encoded so that makes a bit harder to read. Here are the unencoded values :

query = /jcr:root/sites/mySite/tags//element(*, jnt:tag)[jcr:contains(.,'{$q}*')]/@j:nodename
propertyMatchRegexp = {$q}.*

One important thing is to now the $q marker that will actually be replaced with the value of the "q" request parameter specified in the request. This makes it easy to integrate with JQuery plugins that might use specific named request parameters.

HTTP Response :

Content-Length:1721
Content-Type:application/json;charset=UTF-8
Date:Fri, 26 Feb 2010 13:31:38 GMT
Server:Apache-Coyote/1.1

[
  {
    "jcr:score": "1366",
    "node": {
      "index": 1,
      "jcr:createdBy": "root",
      "jcr:baseVersion": "/repository/default/jcr:system/jcr:versionStorage/5b/92/d7/5b92d76a-ee40-4b17-87e6-911a38db2ddb/jcr:rootVersion",
      "jcr:versionHistory": "/repository/default/jcr:system/jcr:versionStorage/5b/92/d7/5b92d76a-ee40-4b17-87e6-911a38db2ddb",
      "depth": 4,
      "jcr:lastModifiedBy": "root",
      "primaryNodeType": "jnt:tag",
      "j:nodename": "tag1",
      "j:originWS": "default",
      "jcr:isCheckedOut": "true",
      "jcr:lastModified": "2010-02-26T12:14:14.502+01:00",
      "matchingProperties": [
        "j:nodename",
        "jcr:isCheckedOut"
      ],
      "jcr:created": "2010-02-26T12:14:14.397+01:00",
      "j:fullpath": "/sites/mySite/tags/tag1",
      "jcr:uuid": "5b92d76a-ee40-4b17-87e6-911a38db2ddb",
      "jcr:primaryType": "jnt:tag",
      "identifier": "5b92d76a-ee40-4b17-87e6-911a38db2ddb",
      "path": "/sites/mySite/tags/tag1"
    },
    "j:nodename": "tag1",
    "jcr:path": "/sites/mySite/tags/tag1"
  },
  {
    "jcr:score": "1366",
    "node": {
      "index": 1,
      "jcr:createdBy": "root",
      "jcr:baseVersion": "/repository/default/jcr:system/jcr:versionStorage/ab/63/bb/ab63bb78-8708-48aa-a040-82eee22ae5d7/jcr:rootVersion",
      "jcr:versionHistory": "/repository/default/jcr:system/jcr:versionStorage/ab/63/bb/ab63bb78-8708-48aa-a040-82eee22ae5d7",
      "depth": 4,
      "jcr:lastModifiedBy": "root",
      "primaryNodeType": "jnt:tag",
      "j:nodename": "tag2",
      "j:originWS": "default",
      "jcr:isCheckedOut": "true",
      "jcr:lastModified": "2010-02-26T12:14:18.570+01:00",
      "matchingProperties": [
        "j:nodename",
        "jcr:isCheckedOut"
      ],
      "jcr:created": "2010-02-26T12:14:18.477+01:00",
      "j:fullpath": "/sites/mySite/tags/tag2",
      "jcr:uuid": "ab63bb78-8708-48aa-a040-82eee22ae5d7",
      "jcr:primaryType": "jnt:tag",
      "identifier": "ab63bb78-8708-48aa-a040-82eee22ae5d7",
      "path": "/sites/mySite/tags/tag2"
    },
    "j:nodename": "tag2",
    "jcr:path": "/sites/mySite/tags/tag2"
  }
]

In the above JSON response, it is important to node the following properties :

jcr:score indicates the score of the result
node contains all the node information, including the matchProperties array that indicates which properties have matched the propertyMatchingRegexp parameter.

Find users or groups

This servlet is dedicated to searching for users or groups, with the particularity of being able to search in external principal repositories connected with Jahia, such as an LDAP repository. Note that the query and result formats are quite different to the default find servlet, because they are not representing JCR nodes but principal data structures.

URL : /cms/findPrincipal

Method : POST or GET

Parameters :

Name Mandatory Description
principalType yes The type of principal you wish to query for. The accepted values are "users" or "groups" a request parameter called "paramName"
siteKey yes (if searching for groups If you have set the principalType parameter to "groups", you will be required to specify a value for the siteKey parameter, which is the site in which you want to search for the groups.
wildcardTerm no If this parameter is specified, it will be used to search for the value specified in any principal property. If you prefer to search for specific properties, you must specify them separately in the HTTP request parameters. You can use the $paramName syntax to reference other request parameters. For example $q will be replaced by the value of a parameter called "q" if it is found. If it is not found the marker will not be replaced.
propertyMatchRexexp no The value you specify here will be used to match properties in the result set, and will build a matchingProperties JSON list that will let you known which properties have matched. This is especially useful when using fulltext search, since the JCR doesn't indicate which property has matched the criteria. The value you specify must comply with the JDK Pattern Regexp syntax.
removeDuplicatePropValues no If activated, it will filter the matched properties (that were matched using the propertyMatchRegexp above parameter) to make sure only one node per property value is returned. This is useful when building unique list of property values for auto-completion. Defaults to false.
includeCriteriaNames no A comma seperated list of parameter names that should be used to create the criterias for the search.
any other parameter name no If you specify another other parameter other than the above reserved ones, the servlet will interpret them as property names that have to to be search for the specified values. You can use the $paramName syntax to reference other request parameters. For example $q will be replaced by the value of a parameter called "q" if it is found. If it is not found the marker will not be replaced. By default the servlet will use all the other found parameter names, which might not be what you expect. You should use the includeCriteriaNames to specify which criteria should be used
 

Example

HTTP GET Request (formatted for legibility)

http://localhost:8080/cms/findPrincipal?principalType=users&wildcardTerm=steve

HTTP Response

[
  {
    "userProperties": "{j:published=true, j:nodename=sjobs, j:originWS=default, jcr:createdBy= system , j:external=false, j:firstName=Steve, jcr:primaryType=jnt:user, j:lastPublished=2010-06-16T16:13:34.094+02:00, j:email=, j:password=943+/myvMchk4TM1T11nKHx2u7g=, j:lastName=Jobs, jcr:baseVersion=164174a4-1ba0-4e3b-8143-119c9546be6c, j:lastPublishedBy= system root, jcr:created=2010-06-16T16:13:33.698+02:00, jcr:versionHistory=d17e5352-4909-48ef-94ef-e5957f741512, jcr:uuid=662cdfed-e674-4dff-85ee-dadb19243f5e, j:fullpath=/users/sjobs, jcr:isCheckedOut=true, j:checkinDate=2010-06-16T16:13:34.094+02:00, jcr:lastModified=2010-06-16T16:14:01.670+02:00, jcr:lastModifiedBy= system , j:organization=}",
    "homepageID": -1,
    "username": "sjobs",
    "providerName": "jcr",
    "userKey": "{jcr}sjobs",
    "root": false,
    "external": false,
    "name": "sjobs",
    "class": "class org.jahia.services.usermanager.jcr.JCRUser",
    "properties": {
      "j:firstName": "Steve",
      "jcr:created": "2010-06-16T16:13:33.698+02:00",
      "j:external": "false",
      "j:originWS": "default",
      "j:published": "true",
      "jcr:lastModified": "2010-06-16T16:14:01.670+02:00",
      "j:lastPublishedBy": " system root",
      "j:checkinDate": "2010-06-16T16:13:34.094+02:00",
      "jcr:createdBy": " system ",
      "j:nodename": "sjobs",
      "jcr:versionHistory": "d17e5352-4909-48ef-94ef-e5957f741512",
      "jcr:lastModifiedBy": " system ",
      "jcr:uuid": "662cdfed-e674-4dff-85ee-dadb19243f5e",
      "preferredLanguage": "en",
      "jcr:baseVersion": "164174a4-1ba0-4e3b-8143-119c9546be6c",
      "j:lastName": "Jobs",
      "jcr:isCheckedOut": "true",
      "j:password": "943+/myvMchk4TM1T11nKHx2u7g=",
      "j:lastPublished": "2010-06-16T16:13:34.094+02:00",
      "j:organization": "",
      "jcr:primaryType": "jnt:user",
      "j:fullpath": "/users/sjobs",
      "j:email": ""
    },
    "identifier": "662cdfed-e674-4dff-85ee-dadb19243f5e",
    "passwordReadOnly": false
  }
]

DEBUGGING

In order to help with the debugging with AJAX REST requests, you can use built-in inspection tools such as :

  • Safari's 4.0 Web Inspector (especially the resources and script tabs)
  • Firefox's Firebug plugin
  • any other equivalents

Property interceptors

Property interceptor catches all accesses to JCR node properties. They may execute an action when a property is stored in the JCR and when it is read from the JCR. It is also possible to make the property set fails if some checks fail.

Interceptors can transform the value before the storage into the JCR and/or after the property is read.

They are system-wide and can be filtered base on the parent node and the property definition.

Several examples of usage for an interceptor are:

Advanced filtering of text e.g. for automatic moderation of blog or forum posts
Auto-tagging and semantic text extraction
Custom URL rewriting in text properties

INTERCEPTOR MECHANISM

By default, interceptors are executed for every get and set operation on a property - but they can be disabled for a specific session. Currently interceptors are disabled when an unlocalized session is used.

When setting a property, the InterceptorChain.beforeSetValue() method is called before setting the value to the JCR. The chain will iterate on all declared interceptors, check if an interceptor needs to be called on the property, and will call the PropertyInterceptor.beforeSetValue(). The interceptor must return the property value, which is then passed to a next interceptor in the chain. If no exception has been thrown when all interceptors have been processed, the last value is set into the property.

The same chain is executed when getting a property - after the property is got from the JCR, RenderChain.afterGetValue() chains all interceptors starting from the last one to the first one, and calls PropertyInterceptor.afterGetValue() on each of them. The final value is returned to the caller.

CONFIGURATION

Core interceptors are declared in the JCRStoreService bean from the applicationContext-jcr.xml file.

Custom interceptor can be declared in a core Spring file or in any module's Spring file as follows:

<bean name="scriptFilteringInterceptor" parent="propertyInterceptorRegistrator">
    <property name="propertyInterceptor">
        <bean class="org.jahia.services.content.interceptor.HtmlFilteringInterceptor" parent="abstractRichTextPropertyInterceptor">
            <property name="filteredTags" value="script"/>
            <property name="removeContentBetweenTags" value="true"/>
        </bean>
    </property>
</bean>

The aforementioned example registers an interceptor to filter out <script/> tags with content from all rich text properties before storing their value into repository.

IMPLEMENTING AN INTERCEPTOR

An interceptor needs to implement the org.jahia.services.content.interceptor.PropertyInterceptor interface. A base implementation class is provided for convenience: org.jahia.services.content.interceptor.BaseInterceptor that already supports some criteria, like node type, property required type, property name or selector type.

Assuming we are implementing a filtering interceptor for forum or blog posts and comments:

<bean name="postFilteringInterceptor" parent="propertyInterceptorRegistrator">
    <property name="propertyInterceptor">
        <bean class="org.jahia.modules.custom.interceptor.PostFilteringInterceptor" parent="abstractRichTextPropertyInterceptor">
            <property name="nodeTypes" value="jnt:post"/>
            <property name="requiredTypes" value="String"/>
        </bean>
    </property>
</bean>

An interceptor in the example above is configured to be applied on text (String) properties of nodes with node type jnt:post.

An implementation of the interceptor can look as follows (for the sake of simplicity the filtering logic is left out):

public class PostFilteringInterceptor extends BaseInterceptor {
    @Override
    public Value beforeSetValue(JCRNodeWrapper node, String name,
                                ExtendedPropertyDefinition definition, Value originalValue) throws RepositoryException {
        String content = originalValue.getString();

        if (content == null || content.length() == 0) {
            return originalValue;
        }

        String result = filter(content);

        return !result.equals(content) ? node.getSession().getValueFactory().createValue(result)
                : originalValue;
    }

    @Override
    public Value[] beforeSetValues(JCRNodeWrapper node, String name,
                                   ExtendedPropertyDefinition definition, Value[] originalValues)
            throws RepositoryException {

        Value[] res = new Value[originalValues.length];

        for (int i = 0; i < originalValues.length; i++) {
            Value originalValue = originalValues[i];
            res[i] = beforeSetValue(node, name, definition, originalValue);
        }
        return res;
    }

    private String filter(String content) {
        // TODO implement text filtering logic here

        return content;
    }
}

Actions

Jahia provides several mechanisms to render your contents, it also provides some to allow end-users or other systems to interact with your content through action on said content. Actions are activated on an HTTP POST request, to comply with the REST methodology. Provided actions allows a lot of interactions already, like adding a comment on any node, starting workflows, sending form submission by mail, register new user, etc.

PRINCIPLES

An Action is a simple class extending the org.jahia.bin.Action abstract class, which is responsible of handling permissions and other restrictions. You need to override the method doExecute to provide your own Action.

public abstract ActionResult doExecute(HttpServletRequest req, RenderContext renderContext, Resource resource,
                                       JCRSessionWrapper session, Map<String, List<String>> parameters,
                                       URLResolver urlResolver)
        throws Exception;

You can do anything you want in an action but keep in mind that the shortest time your action takes to execute is the better end-user experience.

As said previously, Actions are called through HTTP POST methods but, as a result of an action, you can let the user stay on its current web page or redirect to another web page. The result can be a JSON serialized object. This allows to easily interact with framework like JQuery. To describe this behavior, the method doExecute returns a org.jahia.bin.ActionResult object. It contains a HTTP response code and the eventual redirect URL and org.json.JSONObject instance.

Example based on the RateContent Action

public class RateContent extends Action {
    JCRTemplate jcrTemplate;

    public void setJcrTemplate(JCRTemplate jcrTemplate) {
        this.jcrTemplate = jcrTemplate;
    }

    @Override
    public ActionResult doExecute(HttpServletRequest req, RenderContext renderContext, final Resource resource, JCRSessionWrapper session, final Map<String, List<String>> parameters, URLResolver urlResolver) throws Exception {
        return (ActionResult) jcrTemplate.doExecuteWithSystemSession(null,session.getWorkspace().getName(),session.getLocale(),new JCRCallback<Object>() {
            public Object doInJCR(JCRSessionWrapper session) throws RepositoryException {
                JCRNodeWrapper node = session.getNodeByUUID(resource.getNode().getIdentifier());
                if (!node.isNodeType("jmix:rating")) {
                    session.checkout(node);
                    node.addMixin("jmix:rating");
                    session.save();
                }
                List<String> values = parameters.get("j:lastVote");
                node.setProperty("j:lastVote", values.get(0));
                node.setProperty("j:nbOfVotes",node.getProperty("j:nbOfVotes").getLong()+1);
                node.setProperty("j:sumOfVotes",node.getProperty("j:sumOfVotes").getLong()+values.get(0));
                session.save();
                try {
                    return new ActionResult(HttpServletResponse.SC_OK, node.getPath(), Render.serializeNodeToJSON(node));
                } catch (IOException e) {
                    e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
                } catch (JSONException e) {
                    e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
                }
                return null;
            }
        });
    }
}

The org.jahia.bin.Render.serializeNodeToJSON method is a utility function that returns a JSONObject representing a node.

Declaration of an Action is done via Spring. In our example, this action is defined in the rating module like this:

<bean class="org.jahia.modules.rating.actions.RateContent" >
    <property name="name" value="rate"/>
    <property name="jcrTemplate" ref="jcrTemplate"/>
</bean>

CALLING AN ACTION FROM A FORM

Calling an action is simple as calling node/path.actionName.do. In our example for the RateContent Action, here is an extract of a jQuery function in rateable.jsp:

$.post("<c:url value='${url.base}${bindedComponent.path}'/>.rate.do", {'j:lastVote': value,'jcrMethodToCall':"post",
    'jcrCookieName':"rated${bindedComponent.identifier}",
    'jcrCookieValue':"${currentNode.identifier}"}, function(
        result) {
    <%-- Select stars from "Average rating" control to match the returned average rating value --%>
    $("#avg${id}").stars("select", Math.round(result.j_sumOfVotes / result.j_nbOfVotes));
    <%-- Update other text controls... --%>
    $("#all_votes${id}").text(result.j_nbOfVotes);
    $("#all_avg${id}").text(('' + result.j_sumOfVotes / result.j_nbOfVotes).substring(0, 3));
    <%-- Display confirmation message to the user --%>
    $("#messages${id}").html("<br/><fmt:message key="label.ratingSaved"/> (" + value + "). <fmt:message key="label.thanks"/>!").stop().css("opacity", 1).fadeIn(30);
    <%-- Hide confirmation message and enable stars for "Rate this" control, after 2 sec... --%>
    setTimeout(function() {
        $("#messages${id}").fadeOut(1000, function() {
//            ui.enable();
        });
    }, 2000);
}, "json");

Here we see that by posting on the "<c:url value='$url.base$bindedComponent.path'/>.rate.do URL, we are calling the RateContent Action on the bound component.

In rating.hidden.plusone_minorone_form.jsp, you can see another example of calling our Action, this time from a form:

<c:set var="cookieName" value="rated${currentNode.identifier}"/>
<c:if test="${renderContext.loggedIn and (empty cookie[cookieName])}">
    <form action="<c:url value='${url.base}${currentNode.path}'/>.rate.do" method="post"
          id="jahia-forum-post-vote-${currentNode.identifier}">
        <input type="hidden" name="jcrRedirectTo" value="<c:url value='${url.base}${renderContext.mainResource.node.path}'/>"/>
            <%-- Define the output format for the newly created node by default html or by redirectTo--%>
        <input type="hidden" name="jcrNewNodeOutputFormat" value="html"/>
        <input type="hidden" name="jcrMethodToCall" value="post"/>
        <input type="hidden" name="j:lastVote" value="1"/>
        <input type="hidden" name="jcrCookieValue" value="${currentNode.identifier}"/>
        <input type="hidden" name="jcrCookieName" value="${cookieName}"/>
        <input type="hidden" name="jcrReturnContentType" value="html"/>
    </form>
</c:if>

FORM PARAMETERS AND FILE UPLOADING

In an Action, you will receive your form parameters inside the parameters map. Your form submission will pass through several components before reaching your Action. Those components are responsible for managing the security (captcha validation, permissions check) and the uploading of files. If your action needs to manage files, the file will be uploaded in temporary folder so you will only have to choose where to save it in the JCR or elsewhere.
An example of getting an uploaded file in your action and handling it in there :

final FileUpload fu = (FileUpload) req.getAttribute(FileUpload.FILEUPLOAD_ATTRIBUTE);
DiskFileItem inputFile = fu.getFileItems().get("fileField");

This example is from the DocumentConverterAction class.

CHAINING ACTIONS IN ONE REQUEST

Jahia provides a ChainAction class that allows to chain actions called. You will have to pass a chainOfAction parameter containing the list of action to execute - all actions will be executed sequentially with the same parameters, and the the result will be one of the last actions.

For example, the following form will call mail and redirect actions :

<form action="mynode.chain.do" method="post">
    <input type="hidden" name="chainOfAction" value="mail,redirect"/>
    ...
</form>

INTERACTING WITH OTHER JAHIA SUBSYSTEMS

Your action can also interact or be called by other Jahia subsystems like rules or GWT UI; for this your action must also implement the <BackgroundAction> interface:

public class SendAsNewsletterAction extends Action implements BackgroundAction {
    public void executeBackgroundAction(JCRNodeWrapper node) {
        // do local post on node.getPath/sendAsNewsletter.do
        try {
            Map<String,String> headers = new HashMap<String,String>();
            headers.put("jahiatoken",TokenAuthValveImpl.addToken(node.getSession().getUser()));
            String out = httpClientService.executePost("http://localhost:8080"+
                    Jahia.getContextPath() + Render.getRenderServletPath() + "/live/"
                    + node.getResolveSite().getDefaultLanguage() + node.getPath()
                    + ".sendAsNewsletter.do", null, headers);
            logger.info(out);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }
}

Example of usage in rules:

rule "Schedule as a newsletter" 
    when
        A property j:scheduled has been set on a node
            - the node has the type jnt:newsletterIssue
    then
        Log "Scheduling newsletter " + node.getPath() + " at " + propertyValueAsString
        Execute the action "sendAsNewsletter" at j:scheduled on the node
end

Actions: forms validation

Form validation works in two phases, first you have to do some javascript validation on client side. Then Jahia will validate the captcha for you if present, and then your Action could do any validation needed.

Here the code of a typical form, from the add comment example

<template:addResources type="javascript" resources="jquery.js,jquery.validate.js"/>
<template:addResources type="inlinejavascript">
    <script type="text/javascript">
        $(document).ready(function() {
            $("#newCommentForm").validate({
                rules: {
                    'jcr:title': "required",
                    <c:if test="${not renderContext.loggedIn}">
                    pseudo: "required",
                    captcha: "required"
                    </c:if>
                }
            });
        });
    </script>
</template:addResources>

<template:tokenizedForm>
    <form action="<c:url value='${url.base}${boundComponent.path}.addComment.do'/>" method="post" id="newCommentForm">
        <input type="hidden" name="jcrNodeType" value="jnt:post"/>
        <input type="hidden" name="jcrRedirectTo" value="<c:url value='${url.base}${renderContext.mainResource.node.path}'/>"/>
        <input type="hidden" name="jcrNewNodeOutputFormat" value="html"/>
        <input type="hidden" name="jcrResourceID" value="${currentNode.identifier}"/>
        <div id="formGenericComment">
            <fieldset>
                <c:if test="${not renderContext.loggedIn}">
                    <p class="field">
                        <label for="comment_pseudo"><fmt:message key="comment.pseudo"/></label>
                        <input value="${sessionScope.formDatas['pseudo'][0]}"
                               type="text" size="35" name="pseudo" id="comment_pseudo"
                               tabindex="1"/>
                    </p>
                </c:if>
                <p class="field">
                    <label class="left" for="comment-title"><fmt:message key="comment.title"/></label>
                    <input class="" value="${sessionScope.formDatas['jcr:title'][0]}"
                           type="text" size="35" id="comment-title" name="jcr:title"
                           tabindex="1"/>
                </p>
                <p class="field">
                    <label class="left" for="jahia-comment-${boundComponent.identifier}"><fmt:message
                            key="comment.body"/></label>
                    <textarea rows="7" cols="35" id="jahia-comment-${boundComponent.identifier}"
                              name="content"
                              tabindex="2"><c:if
                            test="${not empty sessionScope.formDatas['content']}">${fn:escapeXml(sessionScope.formDatas['content'][0])}</c:if></textarea>
                </p>
                <c:if test="${not renderContext.loggedIn}">
                    <p class="field">
                        <label class="left" for="captcha"><template:captcha/></label>
                        <input type="text" id="captcha" name="jcrCaptcha"/>
                    </p>
                </c:if>
                <p>
                    <input type="reset" value="<fmt:message key='label.reset'/>" class="button"
                           tabindex="3" ${disabled}/>

                    <input type="submit" value="<fmt:message key='label.submit'/>" class="button"
                           tabindex="4" ${disabled}/>
                </p>
            </fieldset>
        </div>
    </form>
</template:tokenizedForm>

First of all you design your form classically

<form action="<c:url value='${url.base}${boundComponent.path}.addComment.do'/>" method="post" id="newCommentForm">
    <div id="formGenericComment">
        <fieldset>
            <c:if test="${not renderContext.loggedIn}">
                <p class="field">
                    <label for="comment_pseudo"><fmt:message key="comment.pseudo"/></label>
                    <input value=""
                           type="text" size="35" name="pseudo" id="comment_pseudo"
                           tabindex="1"/>
                </p>
            </c:if>
            <p class="field">
                <label class="left" for="comment-title"><fmt:message key="comment.title"/></label>
                <input class="" value=""
                       type="text" size="35" id="comment-title" name="jcr:title"
                       tabindex="1"/>
            </p>
            <p class="field">
                <label class="left" for="jahia-comment-${boundComponent.identifier}"><fmt:message
                        key="comment.body"/></label>
                <textarea rows="7" cols="35" id="jahia-comment-${boundComponent.identifier}"
                          name="content"
                          tabindex="2"></textarea>
            </p>
            <p>
                <input type="reset" value="<fmt:message key='label.reset'/>" class="button"
                       tabindex="3"/>

                <input type="submit" value="<fmt:message key='label.submit'/>" class="button"
                       tabindex="4"/>
            </p>
        </fieldset>
    </div>
</form>

Then you add specific hidden fields for Jahia :

<input type="hidden" name="jcrNodeType" value="jnt:post"/>
<input type="hidden" name="jcrRedirectTo" value="<c:url value='${url.base}${renderContext.mainResource.node.path}'/>"/>
<input type="hidden" name="jcrNewNodeOutputFormat" value="html"/>
<input type="hidden" name="jcrResourceID" value="${currentNode.identifier}"/>

Here the AddComment action expects some specific control parameters :

  • <nodeType> defines the type of node this form will create on submission
  • <redirectTo> defines to which node we want to redirect (you can also specify the template here), here we redirect to the current page
  • <newNodeOutputFormat> specify in which format the results will be displayed here <html> (can be ommited as it is the default value)
  • <resourceID> specify the identifier of the currently displayed node to allow the cache system to correctly redisplay the form in case of errors.

ALLOWING CONTENT CREATION IN LIVE WORKSPACE BY AUTHENTICATED OR NON AUTHENTICATED USERS

If your form is designed to be used in live mode (render/live) to allow your users to create content, then you must use the token system of Jahia for your forms and enforce the usage of captcha for at least your non authenticated users.

When Jahia receive data from a tokenized form, it will execute the action with system user. This ensure that anybody seeing the form will be able to execute the action, even if it requires write access. It will also bypass any action restrictions that were set on the action, such as requiredWorkspace, requiredPermission, requiredAuthenticatedUser. If you need to restrict the form display or submission to a specific workspace, you will have to add <c:if> statements around the tokenized form rendering that test the current workspace and also implement checks inside your actions implementation (make sure you do both because a malicious user could always try to submit a HTTP POST request directly to your action, bypassing the HTML form entirely).

First you will need to tokenize your form for that simply wrap your form inside the <template:tokenizedForm> tag

<template:tokenizedForm>
    <form>
        ~~~~~~form code~~~~~~
    </form>
</template:tokenizedForm>

If you look at your html source code in your browser, you will see that the tokenizedForm tag has added an hidden input field in your form containing a uuid that identifies this instance of the form. The uuid is generated by a macro so that the uuid is regenerated each time the form is displayed even if delivered from the cache system.

You also need to add a captcha for at least the non authenticated users to avoid spamming of fake form submission.

To add a captcha, you need to have an input field named <captcha> and display the captcha itself using the <template:captcha> tag.

<c:if test="${not renderContext.loggedIn}">
    <p class="field">
        <label class="left" for="captcha"><template:captcha/></label>
        <input type="text" id="captcha" name="jcrCaptcha"/>
    </p>
</c:if>

VALIDATION OF DATAS BEFORE AND AFTER SUBMISSION

To validate your form on the client side we advise you to use the JQuery Validate plugin. Here an example of configuring it for the previous form:

<template:addResources type="javascript" resources="jquery.js,jquery.validate.js"/>
<template:addResources type="inlinejavascript">
    <script type="text/javascript">
        $(document).ready(function() {
            $("#newCommentForm").validate({
                rules: {
                    'jcr:title': "required",
                    <c:if test="${not renderContext.loggedIn}">
                    pseudo: "required",
                    captcha: "required"
                    </c:if>
                }
            });
        });
    </script>
</template:addResources>

To be noticed that this plugin refer to the field by their <name> attribute and not their <id> attribute.

On token or captcha error Jahia will send you back the submitted datas so you can refill the form with previously submitted datas, so that your users do not have to retype everything.

All submitted datas are store under map in the session of the user. Here an example on a simple filed to display the previously submitted data if needed.

<input class="" value="${sessionScope.formDatas['jcr:title'][0]}"
    type="text" size="35" id="comment-title" name="jcr:title"
    tabindex="1"/>

We see here the formDatas map under the session of the user <$sessionScope.formDatas['jcr:title'][0]>

Actions provided by the default module

This chapter in under construction - if you need more details about this topic, please as directly through the forum.

PERMISSIONS

In order to use some actions, you might need to setup permissions if you intend to perform them with users other than the root user.

GENERAL ACTIONS

addMemberToGroup

chain

This action is used to execute many actions one after the other.

Class: ChainAction.java

URL: /path/to/the/node.chain.do

Parameters:

Name Value Example
chainOfAction comma separated list of action names default,startWorkflow

Example:

<!--
Submitting this form :
1. creates a main content into the home page of ACME-SPACE demo website
2. starts the simple publication workflow  
-->
<form method="post" action="cms/render/default/en/sites/ACME-SPACE/home.chain.do" />
  <input type="hidden" name="chainOfAction" value="default,startWorkflow"/>
 
  <!-- Parameters for the default action (content creation) -->
  <input type="hidden" name="jcrNodeType" value="jnt:mainContent"/>
  <input type="hidden" name="jcr:title" value="Title of my new content"/>
  <input type="hidden" name="body" value="Body of my new content"/>
  <!-- Parameters for StartWorkflowAction -->
  <input type="hidden" name="process" value="jBPM:1-step-publication" />
  <input type="submit" value="Start the actions" />
</form>

lock

unlock

move

multiplePublish

publish

startWorkflow

This action starts a workflow on a node.

Class: StartWorkflowAction.java

URL: /path/to/the/node.startWorkflow.do

Parameters:

Name Value Example
process <workflowProvider>:<workflowKey> jBPM:1-step-publication

Example:

<!-- Submitting this form starts the simple publication workflow on the homepage of ACME-SPACE demo website -->
<form method="post" action="cms/render/default/en/sites/ACME-SPACE/home.startWorkflow.do" />
    <input type="hidden" name="process" value="jBPM:1-step-publication" />
    <input type="submit" value="Start workflow" />
</form>

COMMENTS

addComment

CLIPBOARD

checkClipboard

cleanClipboard

multipleCopy

multipleCut

multipleDelete

multiplePaste

TASKS

commentTask

executeTask

Auto-splitting nodes

OVERVIEW

Automatic splitting feature allows for non-intrusive management of large amount of sub-nodes, based on the configured rules. It is mainly done for maintainability and performance reasons.

Good examples, where the auto-splitting is useful, are site news sections where auto-splitting helps creating news archives, e.g.. by year, month, author or splitting of user activities in Jahia social module.

Splitting can be enabled on a node by adding a jmix:autoSplitFolders mixin type and setting splitting configuration options and node type for split folders.

Split folders are created using the specified node type, e.g. jnt:contentList.

Splitting configuration is a string with semicolon separated split folder settings in the form:

<folder>;<sub-folder>;<sub-sub-folder>

Each single split folder configuration is a string with comma-separated tokens that define how the folder name is determined. Following types of folder configuration are supported:

constant - specifies a predefined name for the split folder. The syntax is:

constant,<folder-name>

For example: constant,reports

property - the name of the folder is determined by the value of the corresponding node property. The syntax is:

property,<property-name>

For example: property,jcr:creator

Additionally the node name can be used if using property,jcr:nodename settings.

firstChars - the name is determined by the first characters of the corresponding property value. The syntax is:

firstChars,<property-name>,<character-count>

For example: firstChars,j:appID,3

substring - name is retrieved as a substring of the corresponding node property value. The syntax is:

substring,<property-name>,<start-position>-<end-position>

For example: substring,j:isbn,3-6. The start-position and end-position indexes are zero-based.

date - name is retrieved by applying the specified date pattern (using SimpleDateFormat) onto the value of the corresponding property. The syntax is:
date,<date-property-name>,<date-pattern>

For example: date,jcr:created,yyyy will create the split folder using the creation year.

As a example, if auto-splitting is enabled on a files node with the configuration:

property,jcr:creator;date,jcr:created,yyyy;date,jcr:created,MM

and split folder node type jnt:contentList.

In such a case the sub-node report.pdf, created in that folder by user sergiy of 1st or July 2010, will land under:

    files
        |_sergiy
               |_2010
                    |_07
                       |_report.pdf

Split folders sergiy, 2010 and 07 will be created automatically using jnt:contentList node type.

JAVA API

Using the Java API auto-splitting on a node can be enabled in the following way:

import org.jahia.services.content.JCRAutoSplitUtils;
...

JCRNodeWrapper node = session.getNode("/shared/files");
JCRAutoSplitUtils.enableAutoSplitting(node,
            "property,jcr:creator;date,jcr:created,yyyy;date,jcr:created,MM",
            "jnt:contentList");

The above given call enables auto-splitting of sub nodes in node myFolderName first by author (creator) than by year and at last by month, creating split "folders" of type jnt:contentList.

RULES

Jahia provides a rule consequence to enable auto-splitting on a node from within business rules. E.g. the following example enables auto-splitting by year and date for user activities (this example taken from Jahia Social Module):

rule "Auto-split user activities node on creation"
  salience 101
  when
    A new node "activities" is created
    The node has a parent
      - the parent has the type jnt:user
  then
    Enable auto-splitting for subnodes of the node into folders of type jnt:contentList using configuration "date,jcr:created,yyyy;date,jcr:created,MM"
end

RESTful JCR Access

Overview

REST, HATEOAS and HTTP

REST stands for REpresentational State Transfer. It is an architectural style defined by R. Fielding that underlies the modern web. On the web, anything of interest can become a resource and be identified using a Universal Resource Identifier (URI). Resources are interacted with using representations that are passed between clients and servers.

Hypermedia As The Engine Of Application State (HATEOAS) is a concept that states that all the information needed to interact with a resource should be contained in its representation. Consider a web site. You first access it by typing its URL (a special kind of URI) in your browser's location bar. The server sends you a representation (HTML page) of the web site's home page (the entry point resource). What you can do with that web page is contained in its representation: links, forms, etc. Of course, you could try to figure out what other resources might exist on that server by manually crafting URIs but it's much easier to follow the information contained in the HTML page. manipulated via representations that are passed between clients and servers. This is the essence of the HATEOAS concept.

HyperText Transfer Protocol (HTTP) is the protocol on which the web is build. It defines a uniform interface that both clients and servers agree to and with which they can manipulate resources. In particular, the protocol defines methods (or verbs) corresponding to operations that can be done on resources. The main methods are:

  • GET: retrieve the identified resource
  • PUT: add / update the identified resource
  • POST: should only be used for complex operations or to create resources from a factory resource (both if needed)
  • DELETE: delete the identified resource

For a good (and not overly complex) overview of REST, please see Jos Dirksen's REST: From GET to HATEOAS presentation.

API version history

  • v1.0: initial release
  • v1.1:
    • PUT and DELETE methods are now supported when operating on nodes via their path
    • added includeFullChildren, resolveReferences and noLinks flags that can be passed as URI query parameters to control some aspects of the representations
    • added new query endpoint to perform JCR-SQL2 queries on the repository and retrieve matching nodes, disabled by default for security reasons, following the jahia.find .disabled property.
    • order of children is now properly maintained
    • it is now possible to filter children to retrieve by providing a list of accepted child node types concatenated by commas
  • v1.1.1:
    • added support for prepared queries to the query endpoint. This is now the preferred way to use the query endpoint since prepared queries will still be available even when the query endpoint is disabled since they are considered "safe" by the administrator who deployed them.
    • flags are now also supported on query endpoint
  • v1.2:
    • support for server-delegated naming of newly created instances via POST requests via both node identifiers or paths
    • added JSON version of the /version endpoint to have an easier to parse version information

Implementation version history

  • v2.0.0: initial release
  • v2.0.1:
    • properly use application/hal+json content type on responses
    • improved links support
    • minor improvements
  • v2.1.0:
    • support for v1.1 of the API
    • fixed an issue with Paths API where extra slashes in the URI could result in unexpected behavior
  • v2.1.1:
    • support for v1.1.1 of the API
    • support for disabling query and types endpoint based on jahia.find.disabled setting
  • v2.2.0:
    • support for v1.2 of the API
    • uses BOM from jaxrs-osgi-extender and json-generation modules to keep Jackson and Jersey versions in sync

Goals

The goals of this project are as follows:

  • Provide a simple Create-Read-Update-Delete (CRUD) RESTful API to JCR content
  • Leverage HATEOAS by providing all the required information to interact with a resource in its representation
  • Support only JSON representations
  • Optimize the API for Javascript client applications
  • Provide full JCR access from the API with the associated benefits and risks

Special provision for PUT and POST methods

PUT and POST methods theoretically exchange full resource representations. However, nodes and properties representations can be quite complex with a potentially deep graph-like structure. Moreover, much of the exposed data is actually read-only (node type information, links...) or is or can be derived from the node type information. It would therefore be inefficient to require clients to pass all that information to the API during creation or update of resources. We adopt the convention that only the information that is either required or is being changed as a result of the operation is passed along to the API. This results in minimal effort on the client side and has the added benefit of reducing the amount of network chatter. The semantics we follow is therefore close to the PATCH method semantics in spirit, if not in implementation.


Resources identification

The natural match to map JCR data unto resources is to use JCR nodes as resources, identified either by their path or identifier, which is made rather easy since JCR data is stored mostly in tree form.

A node also defines sub-resources:

  • children for a given node are accessed using the children child resource
  • properties for a given node are found under the properties child resource
  • mixins for a given node are accessed using the mixins child resource
  • versions for a given node are found under the versions child resource

Each of these sub-resources (which we also call sub-element type as they identify a type of node sub-element) provides named access to their respective sub-elements as well.


URI design

  • : character is encoded by __ in property names since : is a reserved character for URIs
  • indices of same name siblings are denoted using the -- prefix

Examples

Node Encoded URI
/foo/ns:bar/ns:child[2] /foo/ns__bar/ns__child--2
mix:title mixin of /foo /foo/mixins/mix__title
jcr:uuid property of /a /a/properties/jcr__uuid

Basic API workflow

Using the API is then a matter of:

  1. Identifying which resource to work with.
  2. Deciding which HTTP method to use to operate on the identified resource.
  3. Invoke the HTTP method on the resource passing it any necessary data.

Since client and server exchange representations to signify state changes, when you need to pass data to create or update a resource, the API server expects a representation it can understand. In this particular implementation, representations that are expected as input data of operations MUST have the exact same structure as the representation you would retrieve performing a GET operation on that particular resource.

So, for example, if you're trying to add several properties to a given node, you could do it several different ways. Either you could PUT each property individually using each property URI as target URI and passing the JSON representation of a property for each invocation. You could also use the properties sub-resource for this node and invoke the PUT method with a body corresponding to the JSON representation of a properties resource. This second way would result in modifying several properties using a single call to the API.

Note regarding asynchronous calls to the RESTful API: While the API implementation can properly handle concurrent requests at any given time, it is not designed to handle re-entrant requests. This will result in an exception. This could happen if you're calling the API from Javascript and you're calling back to the API in your success callback in an asynchronous call. There normally shouldn't be a need for such calls which is why we are not currently supporting this use case. We might revisit this position if such need arises.


Resources representation

This version of the API will use the JSON representation format as specified by the RFC 4627 augmented by the HAL specification as explained below. The media type for our specific representations for node elements is therefore application/hal+json.

Note that representations elements are not ordered so you shouldn't depend on elements of a given representation being in a specific order. In particular, if elements appear in a given order in examples in this document this doesn't mean that they will appear in the same order in representations you will retrieve from the API.

Note also that we focus only on salient parts in examples so the representations that are shown might actually be incomplete and missing some data that we didn't consider relevant for that specific example. In particular, links sections are often elided in examples for brevity's sake.

Linking to resources and keeping URIs opaque

It is important that clients do not rely on building URIs themselves as much as possible and, thus, we should strive to let clients treat them as opaque as possible. This does not mean that URIs need to be unreadable. This, however, means that URIs for resources should be easily discoverable automatically by clients so that they don't need to construct them. In order to do that, each resource that can be interacted with embeds a _links child object. This child object contains links objects per the HAL specification.

This means that links are represented as objects containing at least an href property identifying the URI associated with the link.

Per the HAL recommendations, we define a self reference identifying the URI to use to interact with this specific element. self is a relative URI so we also provide an absolute link providing the absolute URI for the given resource. If a resource has a type that we can identify then another type link will also be available so that clients can find out more about the resource's metadata. If a resource can be accessed via its path (in JCR parlance), then a path link pointing to the URI allowing access to the resource by its path. When appropriate, another parent link will also be available, pointing to the parent node of the resource. Specific objects might add more link types when appropriate.

We add a little bit of redundant information to make links easier to work with from a javascript client application: we add a rel property to each link that repeats the name of the link from within it so that when iterating over the links collection, we can easily retrieve the name of the relation from an individual link object.

To sum up, the _links section will look similarly to the following example:

"_links" : {
    "self" : {
    "rel" : "self",
    "href" : "<relative URI (to the API base URI) identifying the associated resource>"
    },
    "absolute" : {
    "rel" : "absolute",
    "href" : "<absolute URI identifying the associated resource>"
    },
    "type" : {
    "rel" : "type",
    "href" : "<URI identifying the resource associated with the resource type>"
    }
    ... other links as appropriate
    }

As of version 1.1 of the API, we've added the option not to output links if your client application has no need for them. This is accomplished by providing a query parameter named noLinks to any API URI. If this query parameter is present in the URI, its value is assumed to be true unless its value is false, which corresponds to the default behavior where links are output. Any other value will be understood as true.

Node representation

A node is composed of several elements that need to be represented as efficiently and usefully as possible so that both humans and automated systems can make sense of the information conveyed by the representation. We've identified the following information to represent nodes:

  • The unescaped node's name, represented by the name field, as detailed in the Names section.
  • The name of the node's type, represented by the type field.
  • The node's properties, which are gathered in a properties object, as detailed in the Properties section.
  • The node's children collection, which are gathered in a children object, as detailed in the Children section.
  • The node's attached mixins, which information is gathered in a mixins object, as detailed in the Mixins section.
  • The node's versions if appropriate, which are gathered in a versions object, as detailed in the Versions section.
  • Links to useful resources associated with the node, gathered in _links object. By default, only one level of depth in the hierarchy is retrieved for a given node. This means that reference properties are not resolved to objects but left as strings. This also means that the API makes extensive use of links between resources to discover and interact with associated resources. This is detailed in the Linking section.

The node representation adds URIs identifying each sub-element to the _links section, each named with the name of the sub-element it is associated with. This way, a properties object is added to the _links section pointing to the properties resource, a mixins object points to the mixins resource, etc.

A node's collection resources allow users to query the particular kind of resource it holds or add new resource to the set of existing ones. Collections should also considered as ordered despite not being modelled using JSON arrays. We made this particular design decision because we felt being able to access a child resource via its name was more important than explicitly modelling ordering.

Names, escaped and unescaped

Since JCR property and node names are usually namespaced and the : character used as a namespace delimiter in the prefixed form is a reserved JSON character, we use escaped (according to the rules outlined in the URI design section) names to identify both properties and nodes in collections. This way, client code can refer to properties and nodes without having to first escape them. However, it can still be useful to know the original item name. We therefore provide an extra value in the item content named quite appropriately name which contains the original, unescaped name of the item.

Node representation structure

"name" : "<the node's unescaped name>",
    "type" : "<the node's node type name>",
    "properties" : <properties representation>,
    "mixins" : <mixins representation>,
    "children" : <children representation>,
    "versions" : <versions representation>,
    "_links" : {
    "absolute": {
    "rel": "absolute",
    "href": "<An absolute URL directly usable to retrieve this node's representation>"
    },
    "versions": {
    "rel": "versions",
    "href": "<URI identifying the resource associated with this node's versions>"
    },
    "mixins": {
    "rel": "mixins",
    "href": "<URI identifying the resource associated with this node's mixins>"
    },
    "path": {
    "rel": "path",
    "href": "<URI identifying the URI to access this node by path>"
    },
    "children": {
    "rel": "children",
    "href": "<URI identifying the resource associated with this node's children>"
    },
    "parent": {
    "rel": "parent",
    "href": "<URI identifying the resource associated with this node's parent or itself if the node is the root node>"
    },
    "self": {
    "rel": "self",
    "href": "<URI identifying the resource associated with this node>"
    },
    "properties": {
    "rel": "properties",
    "href": "<URI identifying the resource associated with this node's properties>"
    },
    "type": {
    "rel": "type",
    "href": "<URI identifying the resource associated with this node's type>"
    }
    }

Note that it is possible for an API client to only request a subset of the complete structure. For example, a client might only be interested in properties for a given call and not care about the rest of the structure.

Properties representation

A node's properties are gathered within a properties object that has the following structure:

// other node elements...
    "properties" : {
    <for each property>
    <escaped property name> : <property representation>,
    </for each property>
    "_links" : {
    "absolute" : {
    "rel" : "absolute",
    "href" : "<An absolute URL directly usable to retrieve this properties representation>"
    },
    "parent" : {
    "rel" : "parent",
    "href" : "<URI of the resource associated with the parent node of this properties sub-resource>"
    },
    "self" : {
    "rel" : "self",
    "href" : "<URI of the resource associated with this properties resource>"
    }
    }
    },
    // other node elements...

Each property is represented by an object with the following structure:

"name" : "<unescaped name>",
    "multiValued" : <boolean indicating whether the property is multi-valued or not>
    "reference": <boolean indicating whether the property value(s) point(s) to a node or not>
    "value" : "<value>",
    "type" : "<type>",
    "_links" : {
    "path" : {
    "rel" : "path",
    "href" : "<URI identifying the URI to access this property by path>"
    },
    "absolute" : {
    "rel" : "absolute",
    "href" : "<An absolute URL directly usable to retrieve this property's representation>"
    },
    "parent" : {
    "rel" : "parent",
    "href" : "<URI identifying the resource associated with this property's parent node>"
    },
    "self" : {
    "rel" : "self",
    "href" : "<URI identifying the resource associated with this property>"
    },
    "type" : {
    "rel" : "type",
    "href" : "<URI identifying the resource associated with this property definition>"
    }
    }

type is the case-insensitive name of the JCR property type, and is one of: STRING, BINARY, LONG, DOUBLE, DATE, BOOLEAN, NAME, PATH, REFERENCE, WEAKREFERENCE, URI, and DECIMAL.

multiValued specifies whether the property is multi-valued or not. Having this field allows for easier processing of properties on the client side without having to examine the property's definition.

The reference field specifies whether or not the property is of type PATH, REFERENCE or WEAKREFERENCE; in essence whether or not the property's value is actually a pointer to a node. While this is merely a convenience since that information can be inferred from the type field, this makes client processing easier.

If a property is a reference (its reference field is set to true), an additional target link is added to the _links subsection, providing the URI identifying the resource identified by the path or reference value of the property.

Additionally, if a property is a reference and the resolveReferences flag is set in the URI (using a query parameter appended to the URI), another references subsection will be added to the property's representation containing basic information about the node(s) being referenced by the property value. Each entry in the references object is identified using the identifier of the node being referenced. Note also that the resolveReferences flag works properly with the includeFullChildren one. See the example below for more details.

Examples

An example of the jcr:uuid property of a /sites/mySite node. jcr:uuid is defined by the JCR specification as being defined by the mix:referenceable mixin:

"name" : "jcr:uuid",
    "value" : "039cdef3-289a-4fee-b80e-54da0ad35195",
    "type" : "string",
    "multiValued" : false,
    "reference" : false,
    "_links" : {
    ...
    "self" : { "href" : "<basecontext>/default/en/nodes/039cdef3-289a-4fee-b80e-54da0ad35195/properties/jcr__uuid" },
    "type" : { "href" : "<basecontext>/default/en/paths/jcr__system/jcr__nodeTypes/mix__referenceable/jcr__propertyDefinition" }
    ...
    }

An example of the jcr:mixinTypes property on a /sites/mySite node.

"name" : "jcr:mixinTypes",
    "multiValued" : true,
    "value" : ["jmix:accessControlled" , "jmix:robots"],
    "type" : "string",
    "reference" : false,
    "_links" : {
    ...
    "self" : { "href" : "<basecontext>/default/en/nodes/039cdef3-289a-4fee-b80e-54da0ad35195/properties/jcr__mixinTypes" },
    "type" : { "href" : "<basecontext>/default/en/paths/jcr__system/jcr__nodeTypes/nt__base/jcr__propertyDefinition" }
    ...
    }

An example showing how indexed, same name properties URIs are represented, here the node type associated with the property's definition is the second property defined on the nt:base node type:

"name" : "jcr:primaryType",
    "value" : "jnt:virtualsite",
    "multiValued" : true,
    "type" : "string",
    "reference" : false,
    "_links" : {
    ...
    "type" : { "href" : "<basecontext>/default/en/paths/jcr__system/jcr__nodeTypes/nt__base/jcr__propertyDefinition--2" }
    ...
    }

An example showing how a j:defaultSite reference property pointing to a /sites/mySite node on a /sites node is represented, demonstrating the target field in the _links section:

"name" : "j:defaultSite",
    "value" : "09100a94-0714-4fb6-98de-351ad63773b2",
    "multiValued" : false,
    "type" : "weakreference",
    "reference" : true,
    "_links" : {
    ...
    "type" : { "rel" : "type", "href" : "<basecontext>/default/en/paths/jcr__system/jcr__nodeTypes/jnt__virtualsitesFolder/jcr__propertyDefinition--3" },
    "target" : { "rel" : "target", "href" : "http://api.example.org/sites/mySite" }
    ...
    }

An example showing how the node pointed at by a reference property j:node is resolved in the references subsection of the property's representation when the resolveReferences flag is used in the URI:

"_links": { ... },
    "references": {
    "5c82bcdc-b837-4ee0-a15a-c8d8d48a0916": {
    _links: { ... },
    name: "banner-earth.png",
    type: "jnt:file",
    path: "/sites/ACMESPACE/files/Images/Banner-home-slider/banner-earth.png",
    id: "5c82bcdc-b837-4ee0-a15a-c8d8d48a0916"
    }
    },
    "name": "j:node",
    "type": "WeakReference",
    "path": "/sites/ACMESPACE/home/main/foo/j:node",
    "multiValued": false,
    "value": "5c82bcdc-b837-4ee0-a15a-c8d8d48a0916",
    "reference": true

An example showing how the node pointed at by a reference property j:node is resolved in the references subsection of the property's representation when the resolveReferences flag is used in the URI, in conjunction with the includeFullChildren flag:

"_links": { ... },
    "references": {
    5c82bcdc-b837-4ee0-a15a-c8d8d48a0916: {
    _links: { ... },
    "name": "banner-earth.png",
    "type": "jnt:file",
    "path": "/sites/ACMESPACE/files/Images/Banner-home-slider/banner-earth.png",
    "mixins": { ... },
    "versions": { ... },
    "properties": { ... },
    "children": { ... },
    "id": "5c82bcdc-b837-4ee0-a15a-c8d8d48a0916"
    }
    },
    "name": "j:node",
    "type": "WeakReference",
    "path": "/sites/ACMESPACE/home/main/foo/j:node",
    "multiValued": false,
    "value": "5c82bcdc-b837-4ee0-a15a-c8d8d48a0916",
    "reference": true

Mixins representation

A node's attached mixins information is gathered within a mixins object on the node's representation, as follows:

// other node elements...
    "mixins" : {
    <for each mixin>
    <escaped mixin name> : <mixin representation>,
    </for each mixin>
    "_links" : {
    "absolute" : {
    "rel" : "absolute",
    "href" : "<An absolute URL directly usable to retrieve this mixins sub-resource representation>"
    },
    "parent" : {
    "rel" : "parent",
    "href" : "<URI of the resource associated with the parent node of this mixins sub-resource>"
    },
    "self" : {
    "rel" : "self",
    "href" : "<URI of the resource associated with this nmixins resource>"
    }
    }
    },
    // other node elements...

Here is the structure for a mixin representation:

"name" : <the mixin's unescaped name>,
    "_links" : {
    "absolute" : {
    "rel" : "absolute",
    "href" : "<An absolute URL directly usable to retrieve this mixin's representation>"
    },
    "self" : {
    "rel" : "self",
    "href" : "<URI identifying the resource associated with the mixin in the context of the enclosing node>"
    },
    "type" : {
    "rel" : "type",
    "href" : "<URI identifying the resource associated with the mixin's node type>"
    }
    }

Examples

Given the following mixin definition:

[jmix:robots] mixin
    extends=jnt:virtualsite
    - robots (string, textarea) = 'User-agent: *'

we would get, assuming it is attached to the 49bf6a13-96a8-480a-ae8a-2a82136d1c67 node, a representation similar to:

"_links" : {
    "absolute" : {
    "rel" : "absolute",
    "href" : "http://localhost:8080/modules/api/jcr/v1/default/en/nodes/49bf6a13-96a8-480a-ae8a-2a82136d1c67/mixins/jmix__robots"
    },
    "self" : {
    "rel" : "self",
    "href" : "/api/jcr/v1/default/en/nodes/49bf6a13-96a8-480a-ae8a-2a82136d1c67/mixins/jmix__robots"
    },
    "type" : {
    "rel" : "type",
    "href" : "/api/jcr/v1/default/en/paths/jcr__system/jcr__nodeTypes/jmix__robots"
    }
    },
    "name" : "jmix:robots",
    "properties" : {
    "j:robots" : "String"
    },
    "type" : "jmix:robots"

To attach this mixin to an existing 49bf6a13-96a8-480a-ae8a-2a82136d1c67 node, a client would perform the following, creating a new jmix__robots resource in the mixins collection resource, using a PUT request:

PUT /api/jcr/v1/default/en/nodes/49bf6a13-96a8-480a-ae8a-2a82136d1c67/mixins/jmix__robots HTTP/1.1
    Host: api.example.org

    "properties" : {
    "j__robots" : {
    "value" : "User-agent: *"
    }
    }

Children representation

Children of a given node are gathered within a children object, as follows:

// other node elements...
    "children" : {
    <for each child>
    <escaped child name> : <child representation>,
    </for each child>
    "_links" : {
    "absolute" : {
    "rel" : "absolute",
    "href" : "<An absolute URL directly usable to retrieve this children sub-resource representation>"
    },
    "parent" : {
    "rel" : "parent",
    "href" : "<URI identifying the resource associated with the parent node>"
    },
    "self" : {
    "rel" : "self",
    "href" : "<URI identifying the resource associated with the parent's node children resource>"
    }
    }
    },
    // other node elements...

Each child is represented by an object providing only minimal information about the child: its name, its primary node type and its associated URIs (for both associated node, node type and parent node resources):

"name" : <unescaped child name>,
    "type" : <nodetype name>,
    "id" : <identifier of the child node>,
    "_links" : {
    "absolute": {
    "rel": "absolute",
    "href": "<An absolute URL directly usable to retrieve this node's representation>"
    },
    "path": {
    "rel": "path",
    "href": "<URI identifying the URI to access this node by path>"
    },
    "parent": {
    "rel": "parent",
    "href": "<URI identifying the resource associated with this node's parent>"
    },
    "self": {
    "rel": "self",
    "href": "<URI identifying the resource associated with this node>"
    },
    "type": {
    "rel": "type",
    "href": "<URI identifying the resource associated with this node's type>"
    }
    }

As of version 1.1 of the API, we've added the option to include a full representation for each child instead of simply outputting minimal information as above. This means that now children representations can now be equivalent to that of nodes. This fuller representation is, however, currently limited to only one level of hierarchy, meaning that children of children will use the regular minimal representation. This feature is activated by providing the includeFullChildren query parameter on any API URI. If present, this query parameter is assumed to have a true value, unless a false value is explicitly provided, which corresponds to the default behavior. Any other value is understood to be true.

Also as of version 1.1 of the API, we've added the option to filter children that are returned when asking for a node's children. This is accomplished by providing the childrenNodeTypes query parameter which value is a concatenation of accepted node type names, separated by a comma (','). If this query parameter is present and its value corresponds to a valid list of node type names, only children of the corresponding node type(s) will be output in the node's representation.

Example

Below is the representation of a tags child element of a /sites/mySite node, within the context of the enclosing node children element:

// ...
    "children" : {
    "tags" : {
    "_links" : {
    "path" : {
    "rel" : "path",
    "href" : "/api/jcr/v1/default/en/paths/sites/mySite/tags"
    },
    "absolute" : {
    "rel" : "absolute",
    "href" : "http://localhost:8080/modules/api/jcr/v1/default/en/nodes/e3a6e425-0afa-490b-a319-514db66eea04"
    },
    "parent" : {
    "rel" : "parent",
    "href" : "/api/jcr/v1/default/en/nodes/49bf6a13-96a8-480a-ae8a-2a82136d1c67"
    },
    "self" : {
    "rel" : "self",
    "href" : "/api/jcr/v1/default/en/nodes/e3a6e425-0afa-490b-a319-514db66eea04"
    },
    "type" : {
    "rel" : "type",
    "href" : "/api/jcr/v1/default/en/paths/jcr__system/jcr__nodeTypes/jnt__tagList"
    }
    },
    "name" : "tags",
    "type" : "jnt:tagList",
    "id" : "e3a6e425-0afa-490b-a319-514db66eea04"
    },
    // ...
    "_links" : {
    "absolute" : {
    "rel" : "absolute",
    "href" : "http://localhost:8080/modules/api/jcr/v1/default/en/nodes/49bf6a13-96a8-480a-ae8a-2a82136d1c67/children"
    },
    "parent" : {
    "rel" : "parent",
    "href" : "/api/jcr/v1/default/en/nodes/49bf6a13-96a8-480a-ae8a-2a82136d1c67"
    },
    "self" : {
    "rel" : "self",
    "href" : "/api/jcr/v1/default/en/nodes/49bf6a13-96a8-480a-ae8a-2a82136d1c67/children"
    }
    },
    // ...

If the includeFullChildren flag is provided on the URI, we would get the following representation for the tags child:

// ...
    "children" : {
    "tags" : {
    "_links" : { ... },
    "path": "/sites/mySite/tags",
    "name" : "tags",
    "type" : "jnt:tagList",
    "id" : "e3a6e425-0afa-490b-a319-514db66eea04",
    "mixins": { ... },
    "versions": { ... },
    "properties": { ... },
    "children": { ... } // children would only contain minimal information as "expanding" children is limited to one hierarchic level
    },
    // ...
    "_links" : {
    "absolute" : {
    "rel" : "absolute",
    "href" : "http://localhost:8080/modules/api/jcr/v1/default/en/nodes/49bf6a13-96a8-480a-ae8a-2a82136d1c67/children"
    },
    "parent" : {
    "rel" : "parent",
    "href" : "/api/jcr/v1/default/en/nodes/49bf6a13-96a8-480a-ae8a-2a82136d1c67"
    },
    "self" : {
    "rel" : "self",
    "href" : "/api/jcr/v1/default/en/nodes/49bf6a13-96a8-480a-ae8a-2a82136d1c67/children"
    }
    },
    // ...

Assuming we have a node aNode with children of type ns:foo, ns:bar and ns:baz, adding ?childrenNodeTypes=ns:foo,ns:bar to the node's URI will only return the ns:foo and ns:bar children, omitting any ns:baz ones.

Special considerations for same-name siblings

It is possible for a node type to specify that instances of this node type allow multiple children with the same name. They are then identified using a 1-based index representing the relative position of the child compared to other instances of same-named children. They are created using the children resource of the parent node and are appended at the end of the parent node's children collection. Their URIs and escaped names use the --<index> suffix convention we discussed previously.

For example, assuming a foo node allows for multiple bar children:

# Adding a bar child
    PUT <basecontext>/default/en/nodes/foo/children/bar HTTP/1.1
    Host: api.example.org
    // bar content

    # Response
    HTTP/1.1 200 OK
    Content-Type: application/hal+json

    "name" : "foo",
    // ...
    "children" : {
    "bar" : {
    "name" : "bar",
    "type" : "bar:nodeType",
    // ...
    },
    // ...
    }

    # Adding a bar child
    PUT <basecontext>/default/en/nodes/foo/children/bar HTTP/1.1
    Host: api.example.org
    // another bar child content

    # New response
    HTTP/1.1 200 OK
    Content-Type: application/json

    "name" : "foo",
    // ...
    "children" : {
    "bar" : {
    "name" : "bar",
    "type" : "bar:nodeType",
    // ...
    },
    "bar--2" : {
    "name" : "bar",
    "type" : "bar:nodeType",
    // ...
    },
    // ...
    }

Versions representation

A node's versions are gathered within a versions object as follows:

// other node elements...
    "versions" : {
    <for each version>
    <escaped version name> : <version representation>,
    </for each version>
    "_links" : {
    "absolute" : {
    "rel" : "absolute",
    "href" : "<An absolute URL directly usable to retrieve this versions sub-resource representation>"
    },
    "parent" : {
    "rel" : "parent",
    "href" : "<URI identifying the resource associated with this versions sub-resource's parent>"
    },
    "self" : {
    "rel" : "self",
    "href" : "<URI identifying the resource associated with this node>"
    }
    },
    },
    // other node elements...

Each version is represented as follows:

"_links" : {
    "absolute" : {
    "rel" : "absolute",
    "href" : "<An absolute URL directly usable to retrieve this version's representation>"
    },
    "self" : {
    "rel" : "self",
    "href" : "<URI of the resource associated with this version>"
    },
    "nodeAtVersion" : {
    "rel" : "nodeAtVersion",
    "href" : "<URI corresponding to the node data (frozen node) associated with this version>"
    },
    "next" : {
    "rel" : "next",
    "href" : "<URI of the linear successor of this version if it exists (otherwise this link is skipped)>"
    },
    "previous" : {
    "rel" : "previous",
    "href" : "<URI of the linear predecessor of this version if it exists (otherwise this link is skipped)>"
    }
    },
    "name" : "<unescaped name of this version>",
    "created" : <creation time of this version>

API entry points

The goal of this API is that you should be able to operate on its data using links provided within the returned representations. However, you still need to be able to retrieve that first representation to work with in the first place.

Base context

Since the API implementation is deployed as a module, it is available on your Jahia instance under the /modules context with the /api specific context. Therefore, all URIs targeting the API will start with /modules/api. Keep this in mind while looking at the examples below since we might not repeat the base context all the time.

We further qualify the base context by adding /jcr/v1 to specify that this particular API deals with the JCR domain and is currently in version 1. The scoping by domain allows us to potentially expand the API's reach to other aspects in the future while we also make it clear which version (if/when several versions are needed) of that particular domain API is being used.

<basecontext> will henceforth refer to the /modules/api/jcr/v1 base context below.

Authentication

Access to the JCR content is protected and different users will be able to see different content. It is therefore required to authenticate properly before using the API. In fact, some errors (usually PathNotFoundExceptions) can be the result of attempting to access a node for which you don't have proper access level. There are several options to log into Jahia from clients.

If you use a browser and log in, the API will use your existing session.

From a non-browser client, you have different options. You can programatically log in using the /cms/login URL, POSTing to it as follows: <your Jahia root context>/cms/login?doLogin=true&restMode=true&username=<user name>&password=<user password>&redirectActive=false For example, if you're using cURL, you would do it as follows: curl -i -X POST --cookie-jar cookies.txt '<Jahia context>/cms/login?doLogin=true&restMode=true&username=<user name>&password=<user password>&redirectActive=false' Note that we're using the cookie-jar option. This is needed to record the session information sent back by the server as we then need to provide it for each subsequent request using this mode.

Alternatively, you can use the Authentication HTTP header, using the Basic authentication mode. You can generate a Basic authentication token using your credentials and then provide it using the Authentication header with each request to the API. See Client side Basic HTTP Authentication for more details on how to do this.

API version

You can access the version of the API implementation performing a GET on the <basecontext>/version URI. This returns plain text information about both the version of the API and of the currently running implementation. This can also serve as a quick check to see if the API is currently running or not.

As of version 1.2, if your client requests application/json content, you can also retrieve a JSON representation of the version information from that same URI, as follows:

{
    "api": <API version>,
    "module": <API module implementation version>,
    "commit": {
    "id": <GIT commit identifier>,
    "branch": <commit branch name>
    }
    }

Workspace and language

You can access all the different workspaces and languages available in the Jahia JCR repository. However, you must choose a combination of workspace and language at any one time to work with JCR data. Which workspace and language to use are specified in the URI path, using first, the escaped workspace name followed by the language code associated with the language you wish to retrieve data in.

Therefore, all URIs targeting JCR data will be prefixed as follows: <basecontext>/<workspace name>/<language code>/<rest of the URI>

In the following sections, we detail the different types of URIs the API responds to. We will use <placeholder> or {placeholder} indifferently to represent place holders in the different URIs. Each section will first present the URI template using the JAX-RS @Path syntax for URIs which is quite self-explanatory for anyone with regular expression knowledge. We will then detail each part of the URI template, specify the expected result, define which options if any are available and, finally, which HTTP operations can be used on these URIs. Since we already talked about the workspace and language path elements, we won't address them in the following.

Error reporting

Should an error occur during the processing of an API call, a response using an appropriate HTTP error code should be returned with a JSON body providing some context and details about what went wrong in the form of a JSON object with the following format:

 {
    "exception": <type of the exception that occurred as a fully qualified Java exception name if available>,
    "message": <associated message if any>,
    "operation": <type of operation that triggered the error>,
    "nodeAccess": <how the node on which the error was triggered was accessed: 'byId' if using the nodes entry point or 'byPath' if the paths entry point was used>,
    "idOrPath": <identifier if nodeAccess is byId or path if nodeAccess is byPath of the node that caused the issue>,
    "subElementType": <type of sub-element requested if any>,
    "subElements": <list of requested sub-elements if any>,
    "data": <JSON data that was provided in the request>
    }

Currently the following types of operations exist: read, createOrUpdate, delete which map to GET, PUT and DELETE requests respectively and upload which corresponds to the POST-performed upload method.

Operating on nodes using their identifier

URI template

/{workspace}/{language}/nodes/{id: [^/]*}{subElementType: (/children|mixins|properties|versions)?}{subElement: .*}

URI elements

  • nodes: path element marking access to JCR nodes from their identifier
  • {id: [^/]*}: the identifier of the node we want to operate on, which is defined as all characters up to the next / character
  • {subElementType: (/children|mixins|properties|versions)?}: an optional sub-element type to operate on the identified node's sub-resources as defined in the URI Design section
  • {subElement: .*}: an optional sub-element escaped name to operate on a specific sub-resource of the identified node

If no subElementType path element is provided then no subElement path element can be provided either and the resource on which the API will operate is the node identified by the specified id path element.

If a subElementType path element is provided but no subElement path element is provided, then the API will operate on the collection of specified type of child resources for the node identified by the specified id path element.

If a subElementType path element is provided and a subElement path element is provided, then the API will operate on the child resource identified by the subElement path element for the node identified by the specified id path element.

Allowed HTTP operations

  • GET: to retrieve the identified resource
  • PUT: to create (if it doesn't already exist) or update the identified resource
  • DELETE: to delete the identified resource
  • POST:
    • to create a new child without providing a name for it, leaving it up to the server to create an appropriate one (starting with v1.2 of the API)
    • to rename a resource but leave it at the same spot in the hierarchy using the moveto sub-resource

Accepted data

PUT operations accept JSON representations of the objects that the method intends to update or create, meaning a PUT to create a property must provide a valid JSON representation of a property, a PUT to update a node must provide in the body of the request a valid JSON representation of a node. As mentioned before, this representation only needs to be partial, containing only the information that is required to complete the operation.

DELETE operations accept a JSON array of String identifiers of elements to be batch-deleted. This way several elements can be deleted in one single call.

Examples

GET <basecontext>/default/en/nodes/ will retrieve the root node of the default workspace using its English version when internationalized exists.

GET <basecontext>/live/fr/nodes/children will retrieve only the children of the root node in the live workspace using the French version.

PUT <basecontext>/default/en/nodes/27d671f6-9c75-4604-8f81-0d1861c5e302/children/foo with the {"type" : "jnt:bigText", "properties" : {"text" : {"value" : "FOO!"}}} JSON body data will create a new node of type jnt:bigText named foo with a text property set to FOO! and add it to the children of the node identified with the 27d671f6-9c75-4604-8f81-0d1861c5e302 identifier. Note that this assumes that such a child can be added on that particular node. For example, sending the same request to a node that doesn't accept jnt:bigText children will result in a 500 error response with a body similar to the following one:

 {
    "exception": "javax.jcr.nodetype.ConstraintViolationException",
    "message": "No child node definition for foo found in node /sites/ACMESPACE/home/slider-1/acme-space-demo-carousel",
    "operation": "createOrUpdate",
    "nodeAccess": "byId",
    "idOrPath": "9aa720a1-23d4-454e-ac9c-8bfdf83a8351",
    "subElementType": "children",
    "subElements": ["foo"],
    "data": {
    "type": "jnt:bigText",
    "properties": {
    "text": {
    "multiValued": false,
    "value": "FOO BAR!",
    "reference": false
    }
    }
    }
    }

This body response provides some details as to why this particular operation failed.

PUT <basecontext>/default/en/nodes/27d671f6-9c75-4604-8f81-0d1861c5e302/properties/foo with the {"value" : "bar"} JSON body data will create (or update if it already exists) the foo property of the node identified by the 27d671f6-9c75-4604-8f81-0d1861c5e302 identifier and sets its value to bar.

PUT <basecontext>/default/en//nodes/eae598a3-8a41-4003-9c6b-f31018ee0e46/mixins/jmix__rating with the {"properties" : {"j__lastVote": {"value": "-1"}, "j__nbOfVotes": {"value": "100"}, "j__sumOfVotes": {"value": "1000"}}}' JSON body data will add the jmix:rating mixin on the eae598a3-8a41-4003-9c6b-f31018ee0e46 node, initializing it with the following properties values: j:lastVote set to -1, j:nbOfVotes set to 100 and j:sumOfVotes set to 1000. If the mixin already existed on this node then the properties would only be updated to the specified values. Once that jmix:rating is added to the node, you can then modify its properties as you would do with "normal" properties. For example, PUT <basecontext>/default/en/nodes/eae598a3-8a41-4003-9c6b-f31018ee0e46/properties/j__sumOfVotes with the {"value": "3000"} JSON body data will update the j:sumOfVotes property to 3000.

DELETE <basecontext>/default/en/nodes/eae598a3-8a41-4003-9c6b-f31018ee0e46/mixins/jmix__rating will remove the mixin from the node, while

DELETE <basecontext>/default/en/nodes/eae598a3-8a41-4003-9c6b-f31018ee0e46/properties/j__sumOfVotes will just remove the j:sumOfVotes property.

DELETE <basecontext>/default/en/nodes/eae598a3-8a41-4003-9c6b-f31018ee0e46/properties with the ["j__sumOfVotes", "j__nbOfVotes"] will delete both j:sumOfVotes and j:nbOfVotes properties.

POST <basecontext>/default/en/nodes/eae598a3-8a41-4003-9c6b-f31018ee0e46/moveto/newName will rename the eae598a3-8a41-4003-9c6b-f31018ee0e46 node to newName, leaving it at the same spot in the JCR tree.

As of version 1.2 of the API, POST <basecontext>/default/en/nodes/eae598a3-8a41-4003-9c6b-f31018ee0e46/children with the appropriate JSON body (specifying at least the child's type and omitting its name) will add a new child to eae598a3-8a41-4003-9c6b-f31018ee0e46, automatically generating an appropriate name for it (using its jcr:title property if provided or using its type if not)

Operating on nodes using their path

URI template

/{workspace}/{language}/paths{path: /.*}

URI elements

  • paths: path element marking access to JCR nodes from their path
  • {path: /.*}: the path of the resource to operate one

The path path element should contain the absolute path to a given JCR node with optional sub-element resolution if one of the child resource names defined in the URI Design section is found. Note that once a sub-element is found, the node resolution will occur up to that sub-element and the resolution of the sub-element will happen using the next path element, all others being discarded.

Allowed HTTP operations

  • GET: to retrieve the identified resource
  • PUT: to create (if it doesn't already exist) or update the identified resource (starting from v1.1 of the API)
  • DELETE: to delete the identified resource (starting from v1.1 of the API)
  • POST:
    • to create a new child without providing a name for it, leaving it up to the server to create an appropriate one (starting with v1.2 of the API)
    • to upload a file as a child node of the identified resource using multipart/form-data content type and specifying the file to upload using the file parameter

Examples

<basecontext>/default/en/paths/users/root/profile resolves to the /users/root/profile node in the default workspace using the en language.

<basecontext>/live/fr/paths/sites/foo/properties/bar resolves to the French (fr language) version of the bar property of the /sites/foo node in the live workspace.

<basecontext>/live/fr/paths/sites/foo/properties/bar/baz/foo also resolves to the French version of the bar property of the /sites/foo node since only the next path element is considered when a sub-element type if found in one of the path elements of the considered URI.

POST <basecontext>default/en/paths/users/root/files using multipart/form-data content type and specifying the file to upload using the file parameter will upload the specified file to the /users/root/files content directory. Using cURL: curl -i -H "Authorization:Basic cm9vdDpyb290MTIzNA==" -F file=@file.png <basecontext>/default/en/paths/users/root/files. The API will respond with the new node's representation, including its links and identifier so that you can further work with it.

Retrieving nodes using their type

Version 1.1.1 of the API restricts the types endpoint to limit security exposure. It is therefore disabled by default. Its activation is controlled by the value of the jahia.find.disabled property that can be set in the digital-factory-config/jahia/jahia.properties properties file of your Jahia install. Please refer to the Jahia documentation for more details.

URI template

/{workspace}/{language}/types/{type}

URI elements

  • types: path element marking access to JCR nodes from their type
  • {type}: the escaped name of the type of JCR nodes to retrieve

Options

Options are specified using query parameters in the URI, further refining the request. Here is the list of available query parameters:

  • nameContains: a possibly multi-valued String (by passing the query parameter several time in the URI) specifying which String(s) the retrieved nodes must contain in their name. This is an AND constraint so further value of this parameter further limit the possible names.
  • orderBy: a String specifying whether returned nodes should be ordered by ascending order (the default) or by descending order if the desc value is passed.
  • limit: an integer specifying how many nodes should be returned at most
  • offset: an integer specifying how many nodes are skipped so that paging can be implemented
  • depth: an integer specifying whether the returned nodes hierarchy is expanded to include sub-elements or not (default is 0 so no sub-elements included)

Allowed HTTP operations

  • GET: to retrieve the identified nodes

Examples

GET <basecontext>/default/en/types/genericnt__event?nameContains=jahia will retrieve all the genericnt:event nodes which name contains jahia.

GET <basecontext>/default/en/types/genericnt__event?nameContains=rest&offset=5&limit=10 will retrieve at most 10 genericnt:event nodes starting with the 6th one and which name contains rest.

Querying nodes

Version 1.1.1 of the API restricts the query endpoint introduced in version 1.1 to limit security exposure. It is therefore disabled by default. Its activation is controlled by the value of the jahia.find.disabled property that can be set in the digital-factory-config/jahia/jahia.properties properties file of your Jahia install. Please refer to the Jahia documentation for more details.

URI template

/{workspace}/{language}/query

URI elements

  • query: path element marking access to the query endpoint

Allowed HTTP operations

  • POST: to query the JCR repository

Accepted data

The query endpoint accepts JSON data consisting of the following object structure:

{
    "query" : <A String representing a valid JCR-SQL2 query>,
    "limit" : <An optional Integer specifying the maximum number of to retrieve>,
    "offset": <An optional Integer specifying the starting index of the elements to retrieve to allow for pagination>
    }

As of v1.1.1 of the API, the query endpoint is disabled by default and the preferred way to query nodes is to register prepared queries that are checked by administrators of the server and are deemed "safe". These prepared queries are still available even when the default query endpoint is de-activated since they have been vetted. The accepted JSON input has therefore evolved to support these prepared queries as follows:

{
    "query" : <An optional String representing a valid JCR-SQL2 query>,
    "queryName": <An optional String identifying a registered prepared query>,
    "parameters": <An optional array of Strings providing values for parameter placeholders (the '?' character) in the prepared query>,
    "namedParameters": <An optional dictionary of String -> Object providing values for named parameters in the prepared query>,
    "limit" : <An optional Integer specifying the maximum number of to retrieve>,
    "offset": <An optional Integer specifying the starting index of the elements to retrieve to allow for pagination>
    }

The query value is still supported as previously. However, it will only be taken into account if and only if the query endpoint is activated and no queryName value is provided. Otherwise, the API implementation will look for a prepared query registered under the provided queryName value. A prepared query can specify placeholders to dynamically provide values when the query is run. This is accomplished using two different means. First, for simple cases, you can use the ? character as a placeholder for values to be provided later. In that case, you will need to provide a parameters array of values to replace these placeholders. Order is significant since placeholders are replaced by the provided values in order, the first placeholder being replaced by the first provided value, etc. The other, and preferred, option for complex queries, is to use named parameters with placeholders in the form of a parameter name prefixed by a column (:) and preceded by a space. Parameter names can contain only alpha-numerical and underscore (_) characters. In this case, you will need to provide a namedParameters dictionary providing a mapping between a parameter name and its associated value.

The prepared query will thus be interpolated using the provided values for the parameters and then limited and offset if needed.

Prepared queries are registered using your module Spring context by defining PreparedQuery beans. You will therefore need your module to depend on the jcrestapi module. You need to provide values for the name and source properties, name being the name of the prepared query with which you can access it from the query endpoint and the source property being the query itself, using the appropriate placeholders (either ? or named parameters as you see fit, but you cannot mix both in the same query).

Examples

POST <basecontext>/default/en/query providing the following body {query: "SELECT * FROM [nt:base]", limit: 10, offset: 1} will result in retrieving 10 nodes starting with the second one (i.e. bypassing the first one since the offset is not 0). Note that this query will only work if the query endpoint has been activated (it is disabled by default).

Assuming you've registered the following prepared query in your module's Spring context:

<bean id="foo" class="org.jahia.modules.jcrestapi.api.PreparedQuery">
    <property name="name" value="foo"/>
    <property name="source" value="select * from [nt:nodeType] where [jcr:nodeTypeName] like ?"/>
    </bean>

You can execute the query using POST <basecontext>/default/en/query with the following body {"queryName": "foo", "parameters": [ "nt:%" ] } to execute the following interpolated query on the English version of the default workspace: select * from [nt:nodeType] where [jcr:nodeTypeName] like 'nt:%'.

If, on the other hand, you used the named parameter option:

<bean id="foo" class="org.jahia.modules.jcrestapi.api.PreparedQuery">
    <property name="name" value="foo"/>
    <property name="source" value="select * from [nt:nodeType] where [jcr:nodeTypeName] like :nodeType"/>
    </bean>

You could run the same query using the same request providing the following body this time: {"queryName": "foo", "namedParameters": { "nodeType": "nt:%" } }

JAX-RS endpoints

Developing JAX-RS endpoints for Jahia

Jahia provides a mechanism to publish and register JAX-RS endpoints as OSGi bundles so that Jahia can treat them as regular Jahia modules. This capability provides yet another extension to the Jahia platform by allowing you to create custom, RESTful access to your Jahia content should you find yourself not able to accomplish your task using the other Jahia capabilities.

Architecture overview

Jahia comes bundled with Jersey, which is the JAX-RS reference implementation. Jahia also developed an OSGi extender specifically for JAX-RS endpoints. We based our implementation on Neil Bartlett's JAX-RS Extender Bundle for OSGi that we updated to be able to use JAX-RS 2 and Jersey, along with modifications to make it work seamlessly with Jahia's idiosyncracies. All this means is that you can simply create a JAX-RS endpoint and declaratively register it in Jahia so that it is available to the rest of the platform and any client applications you might have.

Creating your endpoint

Your JAX-RS endpoint needs to be packaged as an OSGi bundle. For more details on OSGi bundles and Digital factory modules, please refer to the Jahia modules documentation.

Since the endpoint is bundled as a JAR file, it has some consequences on what can be done in terms of configuring the application since the web.xml file will not be processed as would be the case for a regular web application. We therefore recommend that you configure your endpoint using the JAX-RS Application mechanism by providing an instance of an Application implementation providing the adequate configuration for your endpoint. Fortunately, Jersey makes this process rather easy thanks to its ResourceConfig class that your Application implementation can extend. Please refer to the Application Deployment and Runtime Environments section of the Jersey documentation for more details.

You then need to tell the JAX-RS extender about your Application implementation so that it can be processed. You also need to specify under which context you want your endpoint to be registered. Note that since your endpoint will be implemented as a Jahia module, the context that you specify actually refers to a sub-context of the Jahia /modules context under which all modules are deployed. All this is accomplished declaratively using specific instructions passed to the maven-bundle-plugin: you specify the fully qualified named of your JAX-RS Application implementation using the JAX-RS -Application directive while the context you want your endpoint to be registered under is specified using the JAX-RS-Alias directive. You can see an example of how this is done with maven below:

    <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <extensions>true</extensions>
        <configuration>
            <instructions>
                <Jahia-Depends>default,...</Jahia-Depends>
                <Private-Package>
                    ...
                </Private-Package>
                <Import-Package>
                    ...
                </Import-Package>
                <JAX-RS-Alias>{the context under which you wish your endpoint to be registered}</JAX-RS-Alias>
                <JAX-RS-Application>{your Application implementation fully qualified class name}</JAX-RS-Application>
            </instructions>
        </configuration>
    </plugin>

If you are not using maven, you can accomplish the same result by modifying the MANIFEST file for your bundle to add these directives. When the JAX-RS OSGi extender processes your bundle, it looks for these directives to perform the necessary operations to register your endpoint with Jahia.

Accessing your endpoint

Assuming you registered your endpoint under the /endpoint context using the JAX-RS-Alias directive, it will accessible under the /modules/endpoint context of your Jahia installation. It will also benefit from all the standard functionality provided by Jahia to its modules, in particular, you can authenticate to Jahia and your credentials will be propagated to your endpoint.