eXo Platform 3.5

## Chapter 1. eXo JCR

eXo provides JCR implementation called eXo JCR.

This part will show you how to configure and use eXo JCR in GateIn and standalone.

## 1.1. Introduction in eXoJCR

Java Content Repository API as well as other Java language related standards is created within the Java Community Process http://jcp.org/ as a result of collaboration of an expert group and the Java community. It is known as JSR-170 (Java Specification Request).

### 1.1.1. Data model

The main purpose of content repository is to maintain the data. The heart of CR is the data model:

- The main data storage abstraction of JCR's data model is a workspace

- Each repository should have one or more workspaces

- The content is stored in a workspace as a hierarchy of items

- Each workspace has its own hierarchy of items

Figure 1.1. Item hierarchy

Node is intended to support the data hierarchy. It is of type using namespaced names which allows the content to be structured in accordance with standardized constraints. A node may be versioned through an associated version graph (optional feature)

Property stored data are values of predefined types (String, Binary, Long, Boolean, Double, Date, Reference, Path).

It is important to note that the data model for the interface (the repository model) is rarely the same as the data models used by the repository's underlying storage subsystems. The repository knows how to make the client's changes persistent because that is part of the repository configuration, rather than part of the application programming task.

## 1.2. Why use JCR?

### 1.2.1. What is JCR?

JCR (Java Content Repository) is a java interface used to access contents that are not only web contents, but also other hierarchically stored data. The content is stored in a repository. The repository can be a file system, a relational database or an XML document. The internal structure of JCR data looks similar to an XML document, that means a document tree with nodes and data, but with a small difference, in JCR the data are stored in "property items".

Or better to cite the specification of JCR: "A content repository is a high-level information management system that is a superset of traditional data repositories."

### 1.2.2. Why use JCR?

How do you know the data of your website are stored? The images are probably in a file system, the meta data are in some dedicated files - maybe in XML - the text documents and pdfs are stored in different folders with the meta data in an other place (a database?) and in a proprietary structure. How do you manage to update these data and how do you manage the access rights? If your boss asks you to manage different versions of each document or not? The larger your website is, the more you need a Content Management Systems (CMS) which tackles all these issues.

These CMS solutions are sold by different vendors and each vendor provides its own API for interfacing the proprietary content repository. The developers have to deal with this and need to learn the vendor-specific API. If in the future you wish to switch to a different vendor, everything will be different and you will have a new implementation, a new interface, etc.

JCR provides a unique java interface for interacting with both text and binary data, for dealing with any kind and amount of meta data your documents might have. JCR supplies methods for storing, updating, deleting and retrieving your data, independent of the fact if this data is stored in a RDBMS, in a file system or as an XML document - you just don't need to care about. The JCR interface is also defined as classes and methods for searching, versioning, access control, locking, and observation.

Furthermore, an export and import functionality is specified so that a switch to a different vendor is always possible.

### 1.2.3. What does eXo do?

eXo fully complies a JCR standard JSR 170; therefore with eXo JCR you can use a vendor-independent API. It means that you could switch any time to a different vendor. Using the standard lowers your lifecycle cost and reduces your long term risk.

Of course eXo does not only offer JCR, but also the complete solution for ECM (Enterprise Content Management) and for WCM (Web Content Management).

### 1.2.4. Further Reading

In order to further understand the theory of JCR and the API, please refer to some external documents about this standard:

- Roy T. Fielding, JSR 170 Overview: Standardizing the Content Repository Interface (March 13, 2005)

- Benjamin Mestrallet, Tuan Nguyen, Gennady Azarenkov, Francois Moron and Brice Revenant eXo Platform v2, Portal, JCR, ECM, Groupware and Business Intelligence. (January 2006)

## 1.3. eXo JCR Implementation

### 1.3.1. Related Documents

Access Control Configuration, Export Import Implementation, External Value Storages, JDBC Data Container config, Locking, Multilanguage support, Node types and Namespaces, Repository and Workspace management, Repository container life cycle, Workspace, Persistence Storage Workspace, SimpleDB storage

### 1.3.2. How it works

eXo Repository Service is a standard eXo service and is a registered IoC component, i.e. can be deployed in some eXo Containers (see Service configuration for details). The relationships between components are shown in the picture below:

eXo Container: some subclasses of org.exoplatform.container.ExoContainer (usually org.exoplatform.container.StandaloneContainer or org.exoplatform.container.PortalContainer) that holds a reference to Repository Service.

- Repository Service: contains information about repositories. eXo JCR is able to manage many Repositories.

- Repository: Implementation of javax.jcr.Repository. It holds references to one or more Workspace(s).

- Workspace: Container of a single rooted tree of Items. (Note that here it is not exactly the same as javax.jcr.Workspace as it is not a per Session object).

Usual JCR application use case includes two initial steps:

- Obtaining Repository object by getting Repository Service from the current eXo Container (eXo "native" way) or via JNDI lookup if eXo repository is bound to the naming context using (see Service configuration for details).

- Creating javax.jcr.Session object that calls Repository.login(..).

### 1.3.3. Workspace Data Model

The following diagram explains which components of eXo JCR implementation are used in a data flow to perform operations specified in JCR API

The Workspace Data Model can be split into 4 levels by data isolation and value from the JCR model point of view.

- eXo JCR core implements JCR API interfaces, such as Item, Node, Property. It contains JCR "logical" view on stored data.

- Session Level: isolates transient data viewable inside one JCR Session and interacts with API level using eXo JCR internal API.

- Session Data Manager: maintains transient session data. With data access/ modification/ validation logic, it contains Modified Items Storage to hold the data changed between subsequent save() calling and Session Items Cache.

- Transaction Data Manager: maintains session data between save() and transaction commit/ rollback if the current session is part of a transaction.

- Workspace Level: operates for particular workspace shared data. It contains per-Workspace objects

- Workspace Storage Data Manager: maintains workspace data, including final validation, events firing, caching.

- Workspace Data Container: implements physical data storage. It allows different types of backend (like RDB, FS files, etc) to be used as a storage for JCR data. With the main Data Container, other storages for persisted Property Values can be configured and used.

- Indexer: maintains workspace data indexing for further queries.

- Storage Level: Persistent storages for:

  - JCR Data

  - Indexes (Apache Lucene)

  - Values (e.g., for BLOBs) if different from the main Data Container

## 1.4. Advantages of eXo JCR

### 1.4.1. Advantages for application developers

- Data repository and application are isolated from each other so an application developer should not learn the details of particular data storage's interfaces, but can need to concentrate on business logic of a particular application built on the top of JCR.

- Repositories can be simply exchanged between different applications without changing the applications themselves. This is the matter of the repository configuration.

- Data storage types/ versions can be changed and also, different types of data storages can be combined in one repository data model (of course, the complexity and work of building interfaces between the repository and its data storage don't disappear but these changes are isolated in the repository and thus manageable from the point of view of the customer).

### 1.4.2. Advantages for managers

- Using a standardized repository for content management reduces the risk of dependence on a particular software vendor and proprietary API.

- Costs for maintaining and developing a content repository based custom application is significantly lower than developing and supporting your own interfaces and maintaining your own data repository applications (staff can be trained once, it is possible to take help from the community and the third party consulters).

- Thanks to flexible layered JCR API (see below), it is possible to fit the legacy storage subsystem into new interfaces and decrease the costs and the risk of losing data.

An extension to the API exists as we can see in the following layer schema.

## 1.5. Compatibility Levels

The Java Content Repository specification JSR-170 has been split into two compliance levels as well as a set of optional features.

Level 1 defines a read-only repository.

Level 2 defines methods for writing content and bidirectional interaction with the repository.

eXo JCR supports JSR-170 level 1 and level 2 and all optional features. The recent JSR-283 is not yet supported.

### 1.5.1. Level 1

Level 1 includes read-only functionality for very simple repositories. It is useful to port an existing data repository and convert it to a more advanced form step by step. JCR uses a well-known Session abstraction to access the repository data (similar to the sessions we have in OS, web, etc).

The features of level 1:

- Initiating a session calling login method with the name of desired workspace and client credentials. It involves some security mechanisms (JAAS) to authenticate the client and in case the client is authorized to use the data from a particular workspace, he can retrieve the session with a workspace tied to it.

- Using the obtained session, the client can retrieve data (items) by traversing the tree, directly accessing a particular item (requesting path or UUID) or traversing the query result. So an application developer can choose the "best" form depending on the content structure and desired operation.

- Reading property values. All content of a repository is ultimately accessed through properties and stored in property values of predefined types (Boolean, Binary Data, Double, Long, String) and special types Name, Reference, and Path. It is possible to read property value without knowing its real name as a primary item.

- Export to XML. Repository supports two XML/JCR data model mappings: system and doc view. The system view provides complete XML serialization without loss of information and is somewhat difficult for a human to read. In contrast, the document view is well readable but does not completely reflect the state of repository, it is used for Xpath queries.

- Query facility with Xpath syntax. Xpath, originally developed for XML, suits the JCR data model as well because the JCR data model is very close to XML's one. It is applied to JCR as it would be applied to the document view of the serialized repository content, returning a table of property names and content matching the query.

- Discovery of available node types. Every node should have only one primary node type that defines names, types and other characteristics of child nodes and properties. It also can have one or more mixin data types that defines additional characteristics. Level 1 provides methods for discovering available in repository node types and node types of a concrete node.

- Transient namespace remapping. Item name can have prefix, delimited by a single ':' (colon) character that indicates the namespace of this name. It is patterned after XML namespaces, prefix is mapped to URI to minimize names collisions. In Level 1, a prefix can be temporary overridden by another prefix in the scope of a session.

### 1.5.2. Level 2

JCR level 2 includes reading/ writing content functionality, importing other sources and managing content definition and structuring using extensible node types.

In addition to the features of the Level 1, it also supports the following major features:

- Adding, moving, copying and removing items inside workspace and moving, copying and cloning items between workspaces. The client can also compare the persisted state of an item with its unsaved states and either save the new state or discard it.

- Modifying and writing value of properties. Property types are checked and can be converted to the defined format.

- Importing XML document into the repository as a tree of nodes and properties. If the XML document is an export of JCR system view, the content of repository can be completely restored. If this is not the case, the document is interpreted as a document view and the import procedure builds a tree of JCR nodes and properties that matches the tree structure of the XML document.

- Assigning node types to nodes. The primary node type is assigned when adding a node. This can be done automatically based on the parent node type definition and mixin node types.

- Persistent namespaces changes. Adding, changing and removing namespaces stored in the namespace registry, excluding built-in namespaces required by JCR.

### 1.5.3. Optional features

On the top of Level 1 or Level 2, a number of optional features are defined for a more advanced repository functionality. This includes functions such as Versioning, (JTA) Transactions, Query using SQL, Explicit Locking and Content Observation. eXo JCR supports all optional features.

## 1.6. Using JCR

### 1.6.1. Obtaining a Repository object

A javax.jcr.Repository object can be obtained by:

- Using the eXo Container "native" mechanism. All Repositories are kept with a single RepositoryService component. So it can be obtained from eXo Container, described as the following:

                RepositoryService repositoryService = (RepositoryService) container.getComponentInstanceOfType(RepositoryService.class);

      Repository repository = repositoryService.getRepository("repositoryName");

- Using the eXo Container "native" mechanism with a thread local saved "current" repository (especially if you plan to use a single repository which covers more than 90% of use cases)

      // set current repository at initial time

      RepositoryService repositoryService = (RepositoryService) container.getComponentInstanceOfType(RepositoryService.class);

      repositoryService.setCurrentRepositoryName("repositoryName");

      ....

      // retrieve and use this repository

      Repository repository = repositoryService.getCurrentRepository();

- Using JNDI as specified in JSR-170. This way you have to configure the reference (see eXo JNDI Naming configuration )

      Context ctx = new InitialContext();

      Repository repository =(Repository) ctx.lookup("repositoryName");

### 1.6.2. JCR Session common considerations

- Remember that javax.jcr.Session is not a thread safe object. Never try to share it between threads.

- Do not use System session from the user related code because a system session has unlimited rights. Call ManageableRepository.getSystemSession() from process related code only.

- Call Session.logout() explicitly to release resources assigned to the session.

- When designing your application, take care of the Session policy inside your application. Two strategies are possible: Stateless (Session per business request) and Stateful (Session per User) or some mix.

### 1.6.3. JCR Application practices

#### Simplifying the management of a multi-workspace application

(one-shot logout for all opened sessions)

Use org.exoplatform.services.jcr.ext.common.SessionProvider which is responsible for caching/obtaining your JCR Sessions and closing all opened sessions at once.

    public class SessionProvider {

      /**

       * Creates a SessionProvider for a certain identity

       * @param cred

       */

      public SessionProvider(Credentials cred)

      /**

       * Gets the session from internal cache or creates and caches a new one

       */

      public Session getSession(String workspaceName, ManageableRepository repository)

        throws LoginException, NoSuchWorkspaceException, RepositoryException

      /**

       * Calls a logout() method for all cached sessions

       */

      public void close()

      /**

       * a Helper for creating a System session provider

       * @return System session

       */

      public static SessionProvider createSystemProvider()

      /**

       * a Helper for creating an Anonimous session provider

       * @return System session

       */

      public static SessionProvider createAnonimProvider()

    }

The SessionProvider is per-request or per-user object, depending on your policy. Create it with your application before performing JCR operations, use it to obtain the Sessions and close at the end of an application session(request). See the following example:

    // (1) obtain current javax.jcr.Credentials, for example get it from AuthenticationService

    Credentials cred = ....

    // (2) create SessionProvider for current user

    SessionProvider sessionProvider = new SessionProvider(ConversationState.getCurrent());

    // NOTE: for creating an Anonymous or System Session use  the corresponding static SessionProvider.create...() method

    // Get appropriate Repository as described in "Obtaining Repository object" section for example

    ManageableRepository repository = (ManageableRepository) ctx.lookup("repositoryName");

    // get an appropriate workspace's session

    Session session = sessionProvider.getSession("workspaceName", repository);

     .........

    // your JCR code

     .........

     // Close the session provider

     sessionProvider.close();

#### Reusing SessionProvider

As shown above, creating the SessionProvider involves multiple steps and you may not want to repeat them each time you need to get a JCR session. In order to avoid all this plumbing code, we provide the SessionProviderService whose goal is to help you to get a SessionProvider object.

The org.exoplatform.services.jcr.ext.app.SessionProviderService interface is defined as follows:

    public interface SessionProviderService {

      void setSessionProvider(Object key, SessionProvider sessionProvider);

      SessionProvider getSessionProvider(Object key);

      void removeSessionProvider(Object key);

    }

Using this service is pretty straightforward, the main contract of an implemented component is getting a SessionProvider by key. eXo provides two implementations :

Implementation | Description | Typical Use
--- | --- | ---
org.exoplatform.services.jcr.ext.app.MapStoredSessionProviderService | per-user style : keeps objects in a Map | per-user. The usual practice uses a user's name or Credentials as a key.
org.exoplatform.services.jcr.ext.app.ThreadLocalSessionProviderService | per-request style : keeps a single SessionProvider in a static ThreadLocal variable | Always use null for the key.

For any implementation, your code should follow the following sequence :

- Call SessionProviderService.setSessionProvider(Object key, SessionProvider sessionProvider) at the beginning of a business request for Stateless application or application's session for Statefull policy.

- Call SessionProviderService.getSessionProvider(Object key) for obtaining a SessionProvider object

- Call SessionProviderService.removeSessionProvider(Object key) at the end of a business request for Stateless application or application's session for Statefull policy.

## 1.7. JCR Service Extensions

### 1.7.1. Concept

eXo JCR supports observation (JSR-170 8.3), which enables applications to register interest in events that describe changes to a workspace, and then monitor and respond to those events. The standard observation feature allows dispatching events when persistent change to the workspace is made.

eXo JCR also offers a proprietary Extension Action which dispatches and fires an event upon each transient session level change, performed by a client. In other words, the event is triggered when a client's program invokes some updating methods in a session or a workspace (such as: Session.addNode(), Session.setProperty(), Workspace.move() etc.

One important recommendation should be applied for an extension action implementation. Each action will add its own execution time to standard JCR methods (Session.addNode(), Session.setProperty(), Workspace.move() etc.) execution time. As a consequence, it's necessary to minimize Action.execute(Context) body execution time.

To make the rule, you can use the dedicated Thread in Action.execute(Context) body for a custom logic. But if your application logic requires the action to add items to a created/updated item and you save these changes immediately after the JCR API method call is returned, the suggestion with Thread is not applicable for you in this case.

### 1.7.2. Implementation

Interceptor framework class diagram

### 1.7.3. Configuration

Add a SessionActionCatalog service and an appropriate AddActionsPlugin (see the example below) configuration to your eXo Container configuration. As usual, the plugin can be configured as in-component-place, which is the case for a Standalone Container or externally, which is a usual case for Root/Portal Container configuration).

Each Action entry is exposed as org.exoplatform.services.jcr.impl.ext.action. ActionConfiguration of actions collection of org.exoplatform.services.jcr.impl.ext.action.AddActionsPlugin$ActionsConfig (see an example below). The mandatory field named actionClassName is the fully qualified name of org.exoplatform.services.command.action.Action implementation - the command will be launched in case the current event matches the criteria. All other fields are criteria. The criteria are *AND*ed together. In other words, for a particular item to be listened to, it must meet ALL the criteria:

* workspace: the comma delimited (ORed) list of workspaces

* eventTypes: a comma delimited (ORed) list of event names (see below) to be listened to. This is the only mandatory field, others are optional and if they are missing they are interpreted as ANY.

* path - a comma delimited (ORed) list of item absolute paths (or within its subtree if isDeep is true, which is the default value)

* nodeTypes - a comma delimited (ORed) list of the current NodeType. Since version 1.6.1 JCR supports the functionalities of nodeType and parentNodeType. This parameter has different semantics, depending on the type of the current item and the operation performed. If the current item is a property it means the parent node type. If the current item is a node, the semantic depends on the event type: ** add node event: the node type of the newly added node. ** add mixin event: the newly added mixing node type of the current node. ** remove mixin event the removed mixin type of the current node. ** other events: the already assigned NodeType(s) of the current node (can be both primary and mixin).

### Note

The list of fields can be extended.

### Note

No spaces between list elements.

### Note

isDeep=false means node, node properties and child nodes.

The list of supported Event names: addNode, addProperty, changeProperty, removeProperty, removeNode, addMixin, removeMixin, lock, unlock, checkin, checkout, read.

    <component>

       <type>org.exoplatform.services.jcr.impl.ext.action.SessionActionCatalog</type>

       <component-plugins>

          <component-plugin>

             <name>addActions</name>

             <set-method>addPlugin</set-method>

             <type>org.exoplatform.services.jcr.impl.ext.action.AddActionsPlugin</type>

             <description>add actions plugin</description>

             <init-params>

                <object-param>

                   <name>actions</name>

                   <object type="org.exoplatform.services.jcr.impl.ext.action.AddActionsPlugin$ActionsConfig">

                   <field  name="actions">

                      <collection type="java.util.ArrayList">

                         <value>

                            <object type="org.exoplatform.services.jcr.impl.ext.action.ActionConfiguration">

                              <field  name="eventTypes"><string>addNode,removeNode</string></field>

                              <field  name="path"><string>/test,/exo:test</string></field>

                              <field  name="isDeep"><boolean>true</boolean></field>

                              <field  name="nodeTypes"><string>nt:file,nt:folder,mix:lockable</string></field>

                              <!-- field  name="workspace"><string>backup</string></field -->

                              <field  name="actionClassName"><string>org.exoplatform.services.jcr.ext.DummyAction</string></field>

                            </object>

                         </value>

                      </collection>

                   </field>

                </object>

              </object-param>

            </init-params>

          </component-plugin>

        </component-plugins>

    </component>

### 1.7.4. Related Pages

Access Control extension

Registry Service

REST Groovy services

## 1.8. eXo JCR Application Model

The following is a picture about the interaction between Applications and JCR:

Every Content (JCR) dependent application interacts with eXo JCR via JSR-170 and eXo JCR API extension (mostly for administration) directly or using some intermediate Framework (Neither Application nor Framework should ever rely on Implementation directly!)

Content Application: all applications may use JCR as a data storage. Some of them are generic and completely decoupled from JCR API as interaction protocol hides Content storage nature (like WebDav client), some partially decoupled (like Command framework based), meaning that they do not use JCR API directly, and some (most part) use JSR-170 directly.

Frameworks is a special kind of JCR client that acts as an intermediate level between Content Repository and End Client Application. There are Protocol (WebDav, RMI or FTP servers for example) and Pattern (Command, Web(servlet), J2EE connector) specific Frameworks. It is possible to build a multi-layered (in framework sense) JCR application, for example Web application uses Web framework that uses Command framework underneath.

## 1.9. NodeType Registration

eXo JCR implementation supports two ways of Nodetypes registration:

- From a NodeTypeValue POJO

- From an XML document (stream)

### 1.9.1. Interfaces and methods

#### ExtendedNodeTypeManager

The ExtendedNodeTypeManager (from JCR 1.11) interface provides the following methods related to registering node types:

    public static final int IGNORE_IF_EXISTS  = 0;

    public static final int FAIL_IF_EXISTS    = 2;

    public static final int REPLACE_IF_EXISTS = 4;

    /**

     * Registers node type using value object.

     */

    void registerNodeType(NodeTypeValue nodeTypeValue, int alreadyExistsBehaviour) throws RepositoryException;

    /**

     * Registers all node types using XML binding value objects from xml stream.

     */

    void registerNodeTypes(InputStream xml, int alreadyExistsBehaviour) throws RepositoryException;

    /**

     * Return <code>NodeTypeValue</code> for a given nodetype name. Used for

     * nodetype update. Value can be edited and registered via

     * <code>registerNodeType(NodeTypeValue nodeTypeValue, int alreadyExistsBehaviour)</code>

     */

    NodeTypeValue getNodeTypeValue(String ntName) throws NoSuchNodeTypeException, RepositoryException;

    /**

     * Registers or updates the specified <code>Collection</code> of

     * <code>NodeTypeValue</code> objects.

     */

    public NodeTypeIterator registerNodeTypes(Collection<NodeTypeValue> values,

                                                int alreadyExistsBehaviour) throws UnsupportedRepositoryOperationException,

                                                                           RepositoryException;

    /**

     * Unregisters the specified node type.

     */

    public void unregisterNodeType(String name) throws UnsupportedRepositoryOperationException,

                                                 NoSuchNodeTypeException,

                                                 RepositoryException;

    /**

     * Unregisters the specified set of node types.<p/> Used to unregister a set

     * of node types with mutual dependencies.

     */

    public void unregisterNodeTypes(String[] names) throws UnsupportedRepositoryOperationException,

                                                     NoSuchNodeTypeException,

                                                     RepositoryException;

#### NodeTypeValue

The NodeTypeValue interface represents a simple container structure used to define node types which are then registered through the ExtendedNodeTypeManager.registerNodeType method. The implementation of this interface does not contain any validation logic.

    /**

     * @return Returns the declaredSupertypeNames.

     */

    public List<String> getDeclaredSupertypeNames();

    /**

     * @param declaredSupertypeNames

     *The declaredSupertypeNames to set.

     */

    public void setDeclaredSupertypeNames(List<String> declaredSupertypeNames);

    /**

     * @return Returns the mixin.

     */

    public boolean isMixin();

    /**

     * @param mixin

     *The mixin to set.

     */

    public void setMixin(boolean mixin);

    /**

     * @return Returns the name.

     */

    public String getName();

    /**

     * @param name

     *The name to set.

     */

    public void setName(String name);

    /**

     * @return Returns the orderableChild.

     */

    public boolean isOrderableChild();

    /**

     * @param orderableChild

     *The orderableChild to set.

     */

    public void setOrderableChild(boolean orderableChild);

    /**

     * @return Returns the primaryItemName.

     */

    public String getPrimaryItemName();

    /**

     * @param primaryItemName

     *The primaryItemName to set.

     */

    public void setPrimaryItemName(String primaryItemName);

    /**

     * @return Returns the declaredChildNodeDefinitionNames.

     */

    public List<NodeDefinitionValue> getDeclaredChildNodeDefinitionValues();

    /**

     * @param declaredChildNodeDefinitionNames

     *The declaredChildNodeDefinitionNames to set.

     */

    public void setDeclaredChildNodeDefinitionValues(List<NodeDefinitionValue> declaredChildNodeDefinitionValues);

    /**

     * @return Returns the declaredPropertyDefinitionNames.

     */

    public List<PropertyDefinitionValue> getDeclaredPropertyDefinitionValues();

    /**

     * @param declaredPropertyDefinitionNames

     *The declaredPropertyDefinitionNames to set.

     */

    public void setDeclaredPropertyDefinitionValues(List<PropertyDefinitionValue> declaredPropertyDefinitionValues);

#### NodeDefinitionValue

The NodeDefinitionValue interface extends ItemDefinitionValue with the addition of writing methods, enabling the characteristics of a child node definition to be set, after that the NodeDefinitionValue is added to a NodeTypeValue.

    /**

     * @return Returns the declaredSupertypeNames.

     */

    public List<String> getDeclaredSupertypeNames();

    /**

     * @param declaredSupertypeNames

     *The declaredSupertypeNames to set.

     */

    public void setDeclaredSupertypeNames(List<String> declaredSupertypeNames);

    /**

     * @return Returns the mixin.

     */

    public boolean isMixin();

    /**

     * @param mixin

     *The mixin to set.

     */

    public void setMixin(boolean mixin);

    /**

     * @return Returns the name.

     */

    public String getName();

    /**

     * @param name

     *The name to set.

     */

    public void setName(String name);

    /**

     * @return Returns the orderableChild.

     */

    public boolean isOrderableChild();

    /**

     * @param orderableChild

     *The orderableChild to set.

     */

    public void setOrderableChild(boolean orderableChild);

    /**

     * @return Returns the primaryItemName.

     */

    public String getPrimaryItemName();

    /**

     * @param primaryItemName

     *The primaryItemName to set.

     */

    public void setPrimaryItemName(String primaryItemName);

    /**

     * @return Returns the declaredChildNodeDefinitionNames.

     */

    public List<NodeDefinitionValue> getDeclaredChildNodeDefinitionValues();

    /**

     * @param declaredChildNodeDefinitionNames

     *The declaredChildNodeDefinitionNames to set.

     */

    public void setDeclaredChildNodeDefinitionValues(List<NodeDefinitionValue> declaredChildNodeDefinitionValues);

    /**

     * @return Returns the declaredPropertyDefinitionNames.

     */

    public List<PropertyDefinitionValue> getDeclaredPropertyDefinitionValues();

    /**

     * @param declaredPropertyDefinitionNames

     *The declaredPropertyDefinitionNames to set.

     */

    public void setDeclaredPropertyDefinitionValues(List<PropertyDefinitionValue> declaredPropertyDefinitionValues);

#### PropertyDefinitionValue

The PropertyDefinitionValue interface extends ItemDefinitionValue with the addition of writing methods, enabling the characteristics of a child property definition to be set, after that the PropertyDefinitionValue is added to a NodeTypeValue.

    /**

     * @return Returns the defaultValues.

     */

    public List<String> getDefaultValueStrings();

    /**

     * @param defaultValues The defaultValues to set.

     */

    public void setDefaultValueStrings(List<String> defaultValues);

    /**

     * @return Returns the multiple.

     */

    public boolean isMultiple();

    /**

     * @param multiple The multiple to set.

     */

    public void setMultiple(boolean multiple);

    /**

     * @return Returns the requiredType.

     */

    public int getRequiredType();

    /**

     * @param requiredType The requiredType to set.

     */

    public void setRequiredType(int requiredType);

    /**

     * @return Returns the valueConstraints.

     */

    public List<String> getValueConstraints();

    /**

     * @param valueConstraints The valueConstraints to set.

     */

    public void setValueConstraints(List<String> valueConstraints);

#### ItemDefinitionValue

     /**

     * @return Returns the autoCreate.

     */

    public boolean isAutoCreate();

    /**

     * @param autoCreate The autoCreate to set.

     */

    public void setAutoCreate(boolean autoCreate);

    /**

     * @return Returns the mandatory.

     */

    public boolean isMandatory();

    /**

     * @param mandatory The mandatory to set.

     */

    public void setMandatory(boolean mandatory);

    /**

     * @return Returns the name.

     */

    public String getName();

    /**

     * @param name The name to set.

     */

    public void setName(String name);

    /**

     * @return Returns the onVersion.

     */

    public int getOnVersion();

    /**

     * @param onVersion The onVersion to set.

     */

    public void setOnVersion(int onVersion);

    /**

     * @return Returns the readOnly.

     */

    public boolean isReadOnly();

    /**

     * @param readOnly The readOnly to set.

     */

    public void setReadOnly(boolean readOnly);

### 1.9.2. Node type registration

eXo JCR implementation supports various methods of the node-type registration.

#### Run time registration from xml file

    ExtendedNodeTypeManager nodeTypeManager = (ExtendedNodeTypeManager) session.getWorkspace()

                                                                 .getNodeTypeManager();

    InputStream is = MyClass.class.getResourceAsStream("mynodetypes.xml");

    nodeTypeManager.registerNodeTypes(is,ExtendedNodeTypeManager.IGNORE_IF_EXISTS );

#### Run time registration using NodeTypeValue

    ExtendedNodeTypeManager nodeTypeManager = (ExtendedNodeTypeManager) session.getWorkspace()

                                                                 .getNodeTypeManager();

    NodeTypeValue testNValue = new NodeTypeValue();

    List<String> superType = new ArrayList<String>();

    superType.add("nt:base");

    testNValue.setName("exo:myNodeType");

    testNValue.setPrimaryItemName("");

    testNValue.setDeclaredSupertypeNames(superType);

    List<PropertyDefinitionValue> props = new ArrayList<PropertyDefinitionValue>();

    props.add(new PropertyDefinitionValue("*",

                                          false,

                                          false,

                                          1,

                                          false,

                                          new ArrayList<String>(),

                                          false,

                                          0,

                                          new ArrayList<String>()));

    testNValue.setDeclaredPropertyDefinitionValues(props);

    nodeTypeManager.registerNodeType(testNValue, ExtendedNodeTypeManager.FAIL_IF_EXISTS);

### 1.9.3. Changing existing node type

If you want to replace existing node type definition, you should pass ExtendedNodeTypeManager.REPLACE_IF_EXISTS as a second parameter for the method ExtendedNodeTypeManager.registerNodeType.

    ExtendedNodeTypeManager nodeTypeManager = (ExtendedNodeTypeManager) session.getWorkspace()

                                                                               .getNodeTypeManager();

    InputStream is = MyClass.class.getResourceAsStream("mynodetypes.xml");

    .....

    nodeTypeManager.registerNodeTypes(is,ExtendedNodeTypeManager.REPLACE_IF_EXISTS );

### 1.9.4. Removing node type

### Note

Node type is possible to remove only when the repository does not contain nodes of this type.

    nodeTypeManager.unregisterNodeType("myNodeType");

### 1.9.5. Practical How to

#### Adding new PropertyDefinition

    NodeTypeValue myNodeTypeValue = nodeTypeManager.getNodeTypeValue(myNodeTypeName);

    List<PropertyDefinitionValue> props = new ArrayList<PropertyDefinitionValue>();

    props.add(new PropertyDefinitionValue("tt",

                                          true,

                                          true,

                                          1,

                                          false,

                                          new ArrayList<String>(),

                                          false,

                                          PropertyType.STRING,

                                          new ArrayList<String>()));

    myNodeTypeValue.setDeclaredPropertyDefinitionValues(props);

    nodeTypeManager.registerNodeType(myNodeTypeValue, ExtendedNodeTypeManager.REPLACE_IF_EXISTS);

#### Adding new child NodeDefinition

    NodeTypeValue myNodeTypeValue = nodeTypeManager.getNodeTypeValue(myNodeTypeName);

    List<NodeDefinitionValue> nodes = new ArrayList<NodeDefinitionValue>();

    nodes.add(new NodeDefinitionValue("child",

                                          false,

                                          false,

                                          1,

                                          false,

                                          "nt:base",

                                          new ArrayList<String>(),

                                          false));

    testNValue.setDeclaredChildNodeDefinitionValues(nodes);

    nodeTypeManager.registerNodeType(myNodeTypeValue, ExtendedNodeTypeManager.REPLACE_IF_EXISTS);

#### Changing or removing existing PropertyDefinition or child NodeDefinition

Note that the existing data must be consistent before changing or removing a existing definition . JCR does not allow you to change the node type in the way in which the existing data would be incompatible with a new node type. But if these changes are needed, you can do it in several phases, consistently changing the node type and the existing data.

For example:

Add a new residual property definition with name "downloadCount" to the existing node type "myNodeType".

There are two limitations that do not allow us to make the task with a single call of registerNodeType method.

- Existing nodes of the type "myNodeType", which does not contain properties "downloadCount" that conflicts with node type what we need.

- Registered node type "myNodeType" will not allow us to add properties "downloadCount" because it has no such specific properties.

To complete the task, we need to make 3 steps:

- Change the existing node type "myNodeType" by adding the mandatory property "downloadCount".

- Add the node type "myNodeType" with the property "downloadCount" to all the existing node types.

- Change the definition of the property "downloadCount" of the node type "myNodeType" to mandatory.

#### Changing the list of super types

    NodeTypeValue testNValue = nodeTypeManager.getNodeTypeValue("exo:myNodeType");

    List<String> superType  = testNValue.getDeclaredSupertypeNames();

    superType.add("mix:versionable");

    testNValue.setDeclaredSupertypeNames(superType);

    nodeTypeManager.registerNodeType(testNValue, ExtendedNodeTypeManager.REPLACE_IF_EXISTS);

## 1.10. Registry Service

### 1.10.1. Concept

The Registry Service is one of the key parts of the infrastructure built around eXo JCR. Each JCR that is based on service, applications, etc may have its own configuration, settings data and other data that have to be stored persistently and used by the approptiate service or application. ( We call it "Consumer").

The service acts as a centralized collector (Registry) for such data. Naturally, a registry storage is JCR based i.e. stored in some JCR workspace (one per Repository) as an Item tree under /exo:registry node.

Despite the fact that the structure of the tree is well defined (see the scheme below), it is not recommended for other services to manipulate data using JCR API directly for better flexibility. So the Registry Service acts as a mediator between a Consumer and its settings.

The proposed structure of the Registry Service storage is divided into 3 logical groups: services, applications and users:

     exo:registry/          <-- registry "root" (exo:registry)
       exo:services/        <-- service data storage (exo:registryGroup)
         service1/
           Consumer data    (exo:registryEntry)
         ...
       exo:applications/    <-- application data storage (exo:registryGroup)
         app1/
           Consumer data    (exo:registryEntry)
         ...
       exo:users/           <-- user personal data storage (exo:registryGroup)
         user1/
           Consumer data    (exo:registryEntry)
         ...

Each upper level eXo Service may store its configuration in eXo Registry. At first, start from xml-config (in jar etc) and then from Registry. In configuration file, you can add force-xml-configuration parameter to component to ignore reading parameters initialization from RegistryService and to use file instead:

    <value-param>

      <name>force-xml-configuration</name>

      <value>true</value>

    </value-param>

### 1.10.2. The API

The main functionality of the Registry Service is pretty simple and straightforward, it is described in the Registry abstract class as the following:

    public abstract class Registry {

      /**

       * Returns the Registry object which wraps the Node of the "exo:registry" type

       */

      public abstract RegistryNode getRegistry(SessionProvider sessionProvider)

          throws RepositoryConfigurationException, RepositoryException;

      /**

       * Returns the existing RegistryEntry which wraps the Node of the "exo:registryEntry" type

       */

      public abstract RegistryEntry getEntry(SessionProvider sessionProvider, String groupName,

          String entryName) throws RepositoryException;

      /**

       * Creates a new RegistryEntry

       */

      public abstract void createEntry(SessionProvider sessionProvider,

          String groupName, RegistryEntry entry) throws RepositoryException;

      /**

       * Replaces a RegistryEntry

       */

      public abstract void recreateEntry(SessionProvider sessionProvider,

          String groupName, RegistryEntry entry) throws RepositoryException;

      /**

       * Removes a RegistryEntry

       */

      public abstract void removeEntry(SessionProvider sessionProvider,

          String groupName, String entryName) throws RepositoryException;

As you can see it looks like a simple CRUD interface for the RegistryEntry object which wraps registry data for some Consumer as a Registry Entry. The Registry Service itself knows nothing about the wrapping data, it is Consumer's responsibility to manage and use its data in its own way.

To create an Entity Consumer you should know how to serialize the data to some XML structure and then create a RegistryEntry from these data at once or populate them in a RegistryEntry object (using RegistryEntry(String entryName) constructor and then obtain and fill a DOM document).

Example of RegistryService using:

        RegistryService regService = (RegistryService) container

        .getComponentInstanceOfType(RegistryService.class);

        RegistryEntry registryEntry = regService.getEntry(sessionProvider,

                RegistryService.EXO_SERVICES, "my-service");

        Document doc = registryEntry.getDocument();

        String mySetting = getElementsByTagName("tagname").item(index).getTextContent();

         .....

### 1.10.3. Configuration

RegistryService has two optional params: value parameter mixin-names and properties parameter locations. The mixin-names is used for adding additional mixins to nodes exo:registry, exo:applications, exo:services, exo:users and exo:groups of RegistryService. This allows the top level applications to manage these nodes in special way. Locations is used to mention where exo:registry is placed for each repository. The name of each property is interpreted as a repository name and its value as a workspace name (a system workspace by default).

    <component>

       <type>org.exoplatform.services.jcr.ext.registry.RegistryService</type>

       <init-params>

          <values-param>

             <name>mixin-names</name>

             <value>exo:hideable</value>

          </values-param>

          <properties-param>

          <name>locations</name>

             <property name="db1" value="ws2"/>

          </properties-param>

       </init-params>

    </component>

## 1.11. Namespace altering

Since version 1.11, eXo JCR implementation supports namespaces altering.

### 1.11.1. Adding new namespace

    ExtendedNamespaceRegistry namespaceRegistry = (ExtendedNamespaceRegistry) workspace.getNamespaceRegistry();

    namespaceRegistry.registerNamespace("newMapping", "http://dumb.uri/jcr");

### 1.11.2. Changing existing namespace

    ExtendedNamespaceRegistry namespaceRegistry = (ExtendedNamespaceRegistry) workspace.getNamespaceRegistry();

    namespaceRegistry.registerNamespace("newMapping", "http://dumb.uri/jcr");

    namespaceRegistry.registerNamespace("newMapping2", "http://dumb.uri/jcr");

### 1.11.3. Removing existing namespace

    ExtendedNamespaceRegistry namespaceRegistry = (ExtendedNamespaceRegistry) workspace.getNamespaceRegistry();

    namespaceRegistry.registerNamespace("newMapping", "http://dumb.uri/jcr");

    namespaceRegistry.unregisterNamespace("newMapping");

## 1.12. Node Types and Namespaces

Support of node types and namespaces is required by the JSR-170 specification. Beyond the methods required by the specification, eXo JCR has its own API extension for the Node type registration as well as the ability to declaratively define node types in the Repository at the start-up time.

### 1.12.1. Node Types definition

Node type registration extension is declared in org.exoplatform.services.jcr.core.nodetype.ExtendedNodeTypeManager interface

Your custom service can register some neccessary predefined node types at the start-up time. The node definition should be placed in a special XML file (see DTD below) and declared in the service's configuration file thanks to eXo component plugin mechanism, described as follows:

    <external-component-plugins>

      <target-component>org.exoplatform.services.jcr.RepositoryService</target-component>

          <component-plugin>

            <name>add.nodeType</name>

            <set-method>addPlugin</set-method>

            <type>org.exoplatform.services.jcr.impl.AddNodeTypePlugin</type>

            <init-params>

              <values-param>

                <name>autoCreatedInNewRepository</name>

                <description>Node types configuration file</description>

                <value>jar:/conf/test/nodetypes-tck.xml</value>

                <value>jar:/conf/test/nodetypes-impl.xml</value>

              </values-param>

        <values-param>

                <name>repo1</name>

                <description>Node types configuration file for repository with name repo1</description>

                <value>jar:/conf/test/nodetypes-test.xml</value>

              </values-param>

        <values-param>

                <name>repo2</name>

                <description>Node types configuration file for repository with name repo2</description>

                <value>jar:/conf/test/nodetypes-test2.xml</value>

              </values-param>

            </init-params>

          </component-plugin>

There are two types of registration. The first type is the registration of node types in all created repositories, it is configured in values-param with the name autoCreatedInNewRepository. The second type is registration of node types in specified repository and it is configured in values-param with the name of repository.

Node type definition file format:

      <?xml version="1.0" encoding="UTF-8"?>

      <!DOCTYPE nodeTypes [

       <!ELEMENT nodeTypes (nodeType)*>

          <!ELEMENT nodeType (supertypes?|propertyDefinitions?|childNodeDefinitions?)>

          <!ATTLIST nodeType

             name CDATA #REQUIRED

             isMixin (true|false) #REQUIRED

             hasOrderableChildNodes (true|false)

             primaryItemName CDATA

          >

          <!ELEMENT supertypes (supertype*)>

          <!ELEMENT supertype (CDATA)>

          <!ELEMENT propertyDefinitions (propertyDefinition*)>

          <!ELEMENT propertyDefinition (valueConstraints?|defaultValues?)>

          <!ATTLIST propertyDefinition

             name CDATA #REQUIRED

             requiredType (String|Date|Path|Name|Reference|Binary|Double|Long|Boolean|undefined) #REQUIRED

             autoCreated (true|false) #REQUIRED

             mandatory (true|false) #REQUIRED

             onParentVersion (COPY|VERSION|INITIALIZE|COMPUTE|IGNORE|ABORT) #REQUIRED

             protected (true|false) #REQUIRED

             multiple  (true|false) #REQUIRED

          >

        <!-- For example if you need to set ValueConstraints [],

          you have to add an empty element <valueConstraints/>.

          The same order is for other properties like defaultValues, requiredPrimaryTypes etc.

          -->

          <!ELEMENT valueConstraints (valueConstraint*)>

          <!ELEMENT valueConstraint (CDATA)>

          <!ELEMENT defaultValues (defaultValue*)>

          <!ELEMENT defaultValue (CDATA)>

          <!ELEMENT childNodeDefinitions (childNodeDefinition*)>

          <!ELEMENT childNodeDefinition (requiredPrimaryTypes)>

          <!ATTLIST childNodeDefinition

             name CDATA #REQUIRED

             defaultPrimaryType  CDATA #REQUIRED

             autoCreated (true|false) #REQUIRED

             mandatory (true|false) #REQUIRED

             onParentVersion (COPY|VERSION|INITIALIZE|COMPUTE|IGNORE|ABORT) #REQUIRED

             protected (true|false) #REQUIRED

             sameNameSiblings (true|false) #REQUIRED

          >

          <!ELEMENT requiredPrimaryTypes (requiredPrimaryType+)>

          <!ELEMENT requiredPrimaryType (CDATA)>

    ]>

### 1.12.2. Namespaces definition

Default namespaces are registered by repository at the start-up time

Your custom service can extend a set of namespaces with some application specific ones, declaring it in service's configuration file thanks to eXo component plugin mechanism, described as follows:

          <component-plugin>

              <name>add.namespaces</name>

              <set-method>addPlugin</set-method>

              <type>org.exoplatform.services.jcr.impl.AddNamespacesPlugin</type>

              <init-params>

                <properties-param>

                  <name>namespaces</name>

                  <property name="test" value="http://www.test.org/test"/>

                </properties-param>

              </init-params>

          </component-plugin>

## 1.13. eXo JCR configuration

This section provides you the knowledge about eXo JCR configuration in details, including the basic and advanced configuration.

### 1.13.1. Related documents

- Configuration persister

- Search Configuration

- JDBC Data Container config

- External Value Storages

- Workspace Persistence Storage

### 1.13.2. Portal and Standalone configuration

Like other eXo services, eXo JCR can be configured and used in the portal or embedded mode (as a service embedded in eXo Portal) and in standalone mode.

In Embedded mode, JCR services are registered in the Portal container and the second option is to use a Standalone container. The main difference between these container types is that the first one is intended to be used in a Portal (Web) environment, while the second one can be used standalone (see the comprehensive page Service Configuration for Beginners for more details).

The following setup procedure is used to obtain a Standalone configuration (see more in Container configuration):

- Configuration that is set explicitly using StandaloneContainer.addConfigurationURL(String url) or StandaloneContainer.addConfigurationPath(String path) before getInstance()

- Configuration from $base:directory/exo-configuration.xml or $base:directory/conf/exo-configuration.xml file. Where $base:directory is either AS's home directory in case of J2EE AS environment or just the current directory in case of a standalone application.

- /conf/exo-configuration.xml in the current classloader (e.g. war, ear archive)

- Configuration from $service_jar_file/conf/portal/configuration.xml. WARNING: Don't rely on some concrete jar's configuration if you have more than one jar containing conf/portal/configuration.xml file. In this case choosing a configuration is unpredictable.

JCR service configuration looks like:

    <component>

      <key>org.exoplatform.services.jcr.RepositoryService</key>

      <type>org.exoplatform.services.jcr.impl.RepositoryServiceImpl</type>

    </component>

    <component>

      <key>org.exoplatform.services.jcr.config.RepositoryServiceConfiguration</key>

      <type>org.exoplatform.services.jcr.impl.config.RepositoryServiceConfigurationImpl</type>

      <init-params>

        <value-param>

          <name>conf-path</name>

          <description>JCR repositories configuration file</description>

          <value>jar:/conf/standalone/exo-jcr-config.xml</value>

        </value-param>

        <properties-param>

          <name>working-conf</name>

          <description>working-conf</description>

          <property name="source-name" value="jdbcjcr" />

          <property name="dialect" value="hsqldb" />

          <property name="persister-class-name" value="org.exoplatform.services.jcr.impl.config.JDBCConfigurationPersister" />

        </properties-param>

      </init-params>

    </component>

conf-path : a path to a RepositoryService JCR Configuration.

working-conf : optional; JCR configuration persister configuration. If there isn't a working-conf, the persister will be disabled.

### 1.13.3. JCR Configuration

The Configuration is defined in an XML file (see DTD below).

JCR Service can use multiple Repositories and each repository can have multiple Workspaces.

From v.1.9 JCR, repositories configuration parameters support human-readable formats of values. They are all case-insensitive:

- Numbers formats: K,KB - kilobytes, M,MB - megabytes, G,GB - gigabytes, T,TB - terabytes. Examples: 100.5 - digit 100.5, 200k - 200 Kbytes, 4m - 4 Mbytes, 1.4G - 1.4 Gbytes, 10T - 10 Tbytes

- Time format endings: ms - milliseconds, m - minutes, h - hours, d - days, w - weeks, if no ending - seconds. Examples: 500ms - 500 milliseconds, 20 - 20 seconds, 30m - 30 minutes, 12h - 12 hours, 5d - 5 days, 4w - 4 weeks.

### 1.13.4. Repository service configuration (JCR repositories configuration)

Service configuration may be placed in jar:/conf/standalone/exo-jcr-config.xml for standalone mode. For portal mode, it is located in the portal web application portal/WEB-INF/conf/jcr/repository-configuration.xml.

default-repository: The name of a default repository (one returned by RepositoryService.getRepository()).

repositories: The list of repositories.

### 1.13.5. Repository configuration

name: The name of a repository.

default-workspace: The name of a workspace obtained using Session's login() or login(Credentials) methods (ones without an explicit workspace name).

system-workspace: The name of workspace where /jcr:system node is placed.

security-domain: The name of a security domain for JAAS authentication.

access-control: The name of an access control policy. There can be 3 types: optional - ACL is created on-demand(default), disable - no access control, mandatory - an ACL is created for each added node(not supported yet).

authentication-policy: The name of an authentication policy class.

workspaces: The list of workspaces.

session-max-age: The time after which an idle session will be removed (called logout). If session-max-age is not set up, idle session will never be removed.

lock-remover-max-threads: Number of threads that can serve LockRemover tasks. Default value is 1. Repository may have many workspaces, each workspace have own LockManager. JCR supports Locks with defined lifetime. Such a lock must be removed is it become expired. That is what LockRemovers does. But LockRemovers is not an independent timer-threads, its a task that executed each 30 seconds. Such a task is served by ThreadPoolExecutor which may use different number of threads.

### 1.13.6. Workspace configuration

name: The name of a workspace

auto-init-root-nodetype: DEPRECATED in JCR 1.9 (use initializer). The node type for root node initialization.

container: Workspace data container (physical storage) configuration.

initializer: Workspace initializer configuration.

cache: Workspace storage cache configuration.

query-handler: Query handler configuration.

auto-init-permissions: DEPRECATED in JCR 1.9 (use initializer). Default permissions of the root node. It is defined as a set of semicolon-delimited permissions containing a group of space-delimited identities (user, group, etc, see Organization service documentation for details) and the type of permission. For example, any read; :/admin read;:/admin add_node; :/admin set_property;:/admin remove means that users from group admin have all permissions and other users have only a 'read' permission.

### 1.13.7. Workspace data container configuration

class: A workspace data container class name.

properties: The list of properties (name-value pairs) for the concrete Workspace data container.

trigger_events_for_descendents_on_rename | indicates if need to trigger events for descendents on rename or not. It allows to increase performance on rename operation but in same time Observation'll not notified, has default value true
lazy-node-iterator-page-size | the page size for lazy iterator. Indicates how many nodes can be retrieved from storage per request. The default value is 100
acl-bloomfilter-false-positive-probability | ACL Bloom-filter desired false positive probability. Range [0..1]. Default value 0.1d. (See the note below)
acl-bloomfilter-elements-number | Expected number of ACL-elements in the Bloom-filter. Default value 1000000. (See the note below)

### Note

Bloom filters are not supported by all the cache implementations so far only the inplementation for infinispan supports it.

value-storages: The list of value storage plugins.

### 1.13.8. Value Storage plugin configuration (for data container):

### Note

The value-storage element is optional. If you don't include it, the values will be stored as BLOBs inside the database.

value-storage: Optional value Storage plugin definition.

class: A value storage plugin class name (attribute).

properties: The list of properties (name-value pairs) for a concrete Value Storage plugin.

filters: The list of filters defining conditions when this plugin is applicable.

### 1.13.9. Initializer configuration (optional)

class: Initializer implementation class.

properties: The list of properties (name-value pairs). Properties are supported.

root-nodetype: The node type for root node initialization.

root-permissions: Default permissions of the root node. It is defined as a set of semicolon-delimited permissions containing a group of space-delimited identities (user, group etc, see Organization service documentation for details) and the type of permission. For example any read; :/admin read;:/admin add_node; :/admin set_property;:/admin remove means that users from group admin have all permissions and other users have only a 'read' permission.

Configurable initializer adds a capability to override workspace initial startup procedure (used for Clustering). Also it replaces workspace element parameters auto-init-root-nodetype and auto-init-permissions with root-nodetype and root-permissions.

### 1.13.10. Cache configuration

enabled: If workspace cache is enabled or not.

class: Cache implementation class, optional from 1.9. Default value is. org.exoplatform.services.jcr.impl.dataflow.persistent.LinkedWorkspaceStorageCacheImpl.

Cache can be configured to use concrete implementation of WorkspaceStorageCache interface. JCR core has two implementation to use:

- LinkedWorkspaceStorageCacheImpl - default, with configurable read behavior and statistic.

- WorkspaceStorageCacheImpl - pre 1.9, still can be used.

properties: The list of properties (name-value pairs) for Workspace cache.

max-size: Cache maximum size (maxSize prior to v.1.9).

live-time: Cached item live time (liveTime prior to v.1.9).

From 1.9 LinkedWorkspaceStorageCacheImpl supports additional optional parameters.

statistic-period: Period (time format) of cache statistic thread execution, 5 minutes by default.

statistic-log: If true cache statistic will be printed to default logger (log.info), false by default or not.

statistic-clean: If true cache statistic will be cleaned after was gathered, false by default or not.

cleaner-period: Period of the eldest items remover execution, 20 minutes by default.

blocking-users-count: Number of concurrent users allowed to read cache storage, 0 - unlimited by default.

### 1.13.11. Query Handler configuration

class: A Query Handler class name.

properties: The list of properties (name-value pairs) for a Query Handler (indexDir).

Properties and advanced features described in Search Configuration.

### 1.13.12. Lock Manager configuration

time-out: Time after which the unused global lock will be removed.

persister: A class for storing lock information for future use. For example, remove lock after jcr restart.

path: A lock folder. Each workspace has its own one.

### Note

Also see lock-remover-max-threads repository configuration parameter.

    <!ELEMENT repository-service (repositories)>

    <!ATTLIST repository-service default-repository NMTOKEN #REQUIRED>

    <!ELEMENT repositories (repository)>

    <!ELEMENT repository (security-domain,access-control,session-max-age,authentication-policy,workspaces)>

    <!ATTLIST repository

      default-workspace NMTOKEN #REQUIRED

      name NMTOKEN #REQUIRED

      system-workspace NMTOKEN #REQUIRED

    >

    <!ELEMENT security-domain (#PCDATA)>

    <!ELEMENT access-control (#PCDATA)>

    <!ELEMENT session-max-age (#PCDATA)>

    <!ELEMENT authentication-policy (#PCDATA)>

    <!ELEMENT workspaces (workspace+)>

    <!ELEMENT workspace (container,initializer,cache,query-handler)>

    <!ATTLIST workspace name NMTOKEN #REQUIRED>

    <!ELEMENT container (properties,value-storages)>

    <!ATTLIST container class NMTOKEN #REQUIRED>

    <!ELEMENT value-storages (value-storage+)>

    <!ELEMENT value-storage (properties,filters)>

    <!ATTLIST value-storage class NMTOKEN #REQUIRED>

    <!ELEMENT filters (filter+)>

    <!ELEMENT filter EMPTY>

    <!ATTLIST filter property-type NMTOKEN #REQUIRED>

    <!ELEMENT initializer (properties)>

    <!ATTLIST initializer class NMTOKEN #REQUIRED>

    <!ELEMENT cache (properties)>

    <!ATTLIST cache

      enabled NMTOKEN #REQUIRED

      class NMTOKEN #REQUIRED

    >

    <!ELEMENT query-handler (properties)>

    <!ATTLIST query-handler class NMTOKEN #REQUIRED>

    <!ELEMENT access-manager (properties)>

    <!ATTLIST access-manager class NMTOKEN #REQUIRED>

    <!ELEMENT lock-manager (time-out,persister)>

    <!ELEMENT time-out (#PCDATA)>

    <!ELEMENT persister (properties)>

    <!ELEMENT properties (property+)>

    <!ELEMENT property EMPTY>

### 1.13.13. Help application to prohibit the use of closed sessions

Products that use eXo JCR, sometimes missuse it since they continue to use a session that has been closed through a method call on a node, a property or even the session itself. To prevent bad practices we propose three modes which are the folllowing:

1. If the system property exo.jcr.prohibit.closed.session.usage has been set to true, then a RepositoryException will be thrown any time an application will try to access to a closed session. In the stack trace, you will be able to know the call stack that closes the session.

2. If the system property exo.jcr.prohibit.closed.session.usage has not been set and the system property exo.product.developing has been set to true, then a warning will be logged in the log file with the full stack trace in order to help identifying the root cause of the issue. In the stack trace, you will be able to know the call stack that closes the session.

3. If none of the previous system properties have been set, then we will ignore that the issue and let the application use the closed session as it was possible before without doing anything in order to allow applications to migrate step by step.

### 1.13.14. Getting the effective configuration at Runtime of all the repositories

The effective configuration of all the repositories and their workspaces can be known thanks to the method getConfigurationXML() that is exposed through JMX at the RepositoryServiceConfiguration level in case of a PortalContainer the name of the related MBean will be of type exo:portal=${portal-container-name},service=RepositoryServiceConfiguration. This method will give you the effective configuration in XML format that has been really interpreted by the the JCR core. This could be helpful to understand how your repositories/workspaces are configured especially if you would like to overwrite the configuration for some reasons.

## 1.14. Multi-language support in eXo JCR RDB backend

Whenever relational database is used to store multilingual text data of eXo Java Content Repository, it is necessary to adapt configuration in order to support UTF-8 encoding. Here is a short HOWTO instruction for several supported RDBMS with examples.

The configuration file you have to modify: .../webapps/portal/WEB-INF/conf/jcr/repository-configuration.xml

### Note

Datasource `jdbcjcr` used in examples can be configured via `InitialContextInitializer` component.

### 1.14.1. Oracle

In order to run multilanguage JCR on an Oracle backend Unicode encoding for characters set should be applied to the database. Other Oracle globalization parameters don't make any impact. The only property to modify is `NLS_CHARACTERSET`.

We have tested `NLS_CHARACTERSET` = `AL32UTF8` and it works well for many European and Asian languages.

Example of database configuration (used for JCR testing):

    NLS_LANGUAGE             AMERICAN
    NLS_TERRITORY            AMERICA
    NLS_CURRENCY             $
    NLS_ISO_CURRENCY         AMERICA
    NLS_NUMERIC_CHARACTERS   .,
    NLS_CHARACTERSET         AL32UTF8
    NLS_CALENDAR             GREGORIAN
    NLS_DATE_FORMAT          DD-MON-RR
    NLS_DATE_LANGUAGE        AMERICAN
    NLS_SORT                 BINARY
    NLS_TIME_FORMAT          HH.MI.SSXFF AM
    NLS_TIMESTAMP_FORMAT     DD-MON-RR HH.MI.SSXFF AM
    NLS_TIME_TZ_FORMAT       HH.MI.SSXFF AM TZR
    NLS_TIMESTAMP_TZ_FORMAT  DD-MON-RR HH.MI.SSXFF AM TZR
    NLS_DUAL_CURRENCY        $
    NLS_COMP                 BINARY
    NLS_LENGTH_SEMANTICS     BYTE
    NLS_NCHAR_CONV_EXCP      FALSE
    NLS_NCHAR_CHARACTERSET   AL16UTF16

### Warning

JCR 1.12.x doesn't use NVARCHAR columns, so that the value of the parameter NLS_NCHAR_CHARACTERSET does not matter for JCR.

Create database with Unicode encoding and use Oracle dialect for the Workspace Container:

    <workspace name="collaboration">

              <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

                <properties>

                  <property name="source-name" value="jdbcjcr" />

                  <property name="dialect" value="oracle" />

                  <property name="multi-db" value="false" />

                  <property name="max-buffer-size" value="200k" />

                  <property name="swap-directory" value="target/temp/swap/ws" />

                </properties>

              .....

### 1.14.2. DB2

DB2 Universal Database (DB2 UDB) supports UTF-8 and UTF-16/UCS-2. When a Unicode database is created, CHAR, VARCHAR, LONG VARCHAR data are stored in UTF-8 form. It's enough for JCR multi-lingual support.

Example of UTF-8 database creation:

    DB2 CREATE DATABASE dbname USING CODESET UTF-8 TERRITORY US

Create database with UTF-8 encoding and use db2 dialect for Workspace Container on DB2 v.9 and higher:

    <workspace name="collaboration">

              <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

                <properties>

                  <property name="source-name" value="jdbcjcr" />

                  <property name="dialect" value="db2" />

                  <property name="multi-db" value="false" />

                  <property name="max-buffer-size" value="200k" />

                  <property name="swap-directory" value="target/temp/swap/ws" />

                </properties>

              .....

### Note

For DB2 v.8.x support change the property "dialect" to db2v8.

### 1.14.3. MySQL

JCR MySQL-backend requires special dialect MySQL-UTF8 to be used for internationalization support. But the database default charset should be latin1 to use limited index space effectively (1000 bytes for MyISAM engine, 767 for InnoDB). If database default charset is multibyte, a JCR database initialization error is thrown concerning index creation failure. In other words, JCR can work on any singlebyte default charset of database, with UTF8 supported by MySQL server. But we have tested it only on latin1 database default charset.

Repository configuration, workspace container entry example:

    <workspace name="collaboration">

              <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

                <properties>

                  <property name="source-name" value="jdbcjcr" />

                  <property name="dialect" value="mysql-utf8" />

                  <property name="multi-db" value="false" />

                  <property name="max-buffer-size" value="200k" />

                  <property name="swap-directory" value="target/temp/swap/ws" />

                </properties>

              .....

### 1.14.4. PostgreSQL

On PostgreSQL-backend, multilingual support can be enabled in different ways:

- Using the locale features of the operating system to provide locale-specific collation order, number formatting, translated messages, and other aspects. UTF-8 is widely used on Linux distributions by default, so it can be useful in such case.

- Providing a number of different character sets defined in the PostgreSQL server, including multiple-byte character sets, to support storing text of any languages, and providing character set translation between client and server. We recommend to use UTF-8 database charset, it will allow any-to-any conversations and make this issue transparent for the JCR.

Create database with UTF-8 encoding and use PgSQL dialect for Workspace Container:

    <workspace name="collaboration">

              <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

                <properties>

                  <property name="source-name" value="jdbcjcr" />

                  <property name="dialect" value="pgsql" />

                  <property name="multi-db" value="false" />

                  <property name="max-buffer-size" value="200k" />

                  <property name="swap-directory" value="target/temp/swap/ws" />

                </properties>

              .....

## 1.15. How to host several JCR instances on the same database instance?

Frequently, a single database instance must be shared by several other applications. But some of our customers have also asked for a way to host several JCR instances in the same database instance. To fulfill this need, we had to review our queries and scope them to the current schema; it is now possible to have one JCR instance per DB schema instead of per DB instance. To benefit of the work done for this feature you will need to apply the configuration changes described below.

### 1.15.1. LockManager configuration

To enable this feature you need to replace org.jboss.cache.loader.JDBCCacheLoader with org.exoplatform.services.jcr.impl.core.lock.jbosscache.JDBCCacheLoader in JBossCache configuration file.

Here is an example of this very part of the configuration:

    <jbosscache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:jboss:jbosscache-core:config:3.1">

       <locking useLockStriping="false" concurrencyLevel="500" lockParentForChildInsertRemove="false"

          lockAcquisitionTimeout="20000" />

       <clustering mode="replication" clusterName="${jbosscache-cluster-name}">

          <stateRetrieval timeout="20000" fetchInMemoryState="false" />

          <sync />

       </clustering>

       <loaders passivation="false" shared="true">

          <!-- All the data of the JCR locks needs to be loaded at startup -->

          <preload>

             <node fqn="/" />

          </preload>

          <!--

          For another cache-loader class you should use another template with

          cache-loader specific parameters

          -->

          <loader class=

     async="false" fetchPersistentState="false"

             ignoreModifications="false" purgeOnStartup="false">

             <properties>

                cache.jdbc.table.name=${jbosscache-cl-cache.jdbc.table.name}

                cache.jdbc.table.create=${jbosscache-cl-cache.jdbc.table.create}

                cache.jdbc.table.drop=${jbosscache-cl-cache.jdbc.table.drop}

                cache.jdbc.table.primarykey=${jbosscache-cl-cache.jdbc.table.primarykey}

                cache.jdbc.fqn.column=${jbosscache-cl-cache.jdbc.fqn.column}

                cache.jdbc.fqn.type=${jbosscache-cl-cache.jdbc.fqn.type}

                cache.jdbc.node.column=${jbosscache-cl-cache.jdbc.node.column}

                cache.jdbc.node.type=${jbosscache-cl-cache.jdbc.node.type}

                cache.jdbc.parent.column=${jbosscache-cl-cache.jdbc.parent.column}

                cache.jdbc.datasource=${jbosscache-cl-cache.jdbc.datasource}

             </properties>

          </loader>

       </loaders>

    </jbosscache>

You can also obtain file exmaple from svn.

### 1.15.2. HibernateService configuration

If you use HibernateService for JDBC connections management you will need to specify explicitly the default schema by setting "hibernate.default_schema" property in the configuration of HibernateService.

Here is an example:

    <component>

        <key>org.exoplatform.services.database.HibernateService</key>

        <jmx-name>database:type=HibernateService</jmx-name>

        <type>org.exoplatform.services.database.impl.HibernateServiceImpl</type>

        <init-params>

          <properties-param>

            <name>hibernate.properties</name>

            <description>Default Hibernate Service</description>

            ...........

          </properties-param>

        </init-params>

    </component>

## 1.16. Search Configuration

Search is an important function in eXo JCR, so it is very necessary for you to know how to configure the eXo JCR Search tool.

### 1.16.1. XML Configuration

JCR index configuration. You can find this file here: `.../portal/WEB-INF/conf/jcr/repository-configuration.xml`

    <repository-service default-repository="db1">

      <repositories>

        <repository name="db1" system-workspace="ws" default-workspace="ws">

           ....

          <workspaces>

            <workspace name="ws">

           ....

              <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

                <properties>

                  <property name="index-dir" value="${java.io.tmpdir}/temp/index/db1/ws" />

                  <property name="synonymprovider-class" value="org.exoplatform.services.jcr.impl.core.query.lucene.PropertiesSynonymProvider" />

                  <property name="synonymprovider-config-path" value="/synonyms.properties" />

                  <property name="indexing-config-path" value="/indexing-configuration.xml" />

                  <property name="query-class" value="org.exoplatform.services.jcr.impl.core.query.QueryImpl" />

                </properties>

              </query-handler>

            ...

            </workspace>

         </workspaces>

        </repository>

      </repositories>

    </repository-service>

### 1.16.2. Configuration parameters

Parameter | Default | Description | Since
--- | --- | --- | ---
index-dir | none | The location of the index directory. This parameter is mandatory. Up to 1.9, this parameter called "indexDir" | 1.0
use-compoundfile | true | Advises lucene to use compound files for the index files. | 1.9
min-merge-docs | 100 | Minimum number of nodes in an index until segments are merged. | 1.9
volatile-idle-time | 3 | Idle time in seconds until the volatile index part is moved to a persistent index even though minMergeDocs is not reached. | 1.9
max-merge-docs | Integer.MAX_VALUE | Maximum number of nodes in segments that will be merged. The default value changed in JCR 1.9 to Integer.MAX_VALUE. | 1.9
merge-factor | 10 | Determines how often segment indices are merged. | 1.9
max-field-length | 10000 | The number of words that are fulltext indexed at most per property. | 1.9
cache-size | 1000 | Size of the document number cache. This cache maps uuids to lucene document numbers | 1.9
force-consistencycheck | false | Runs a consistency check on every startup. If false, a consistency check is only performed when the search index detects a prior forced shutdown. | 1.9
auto-repair | true | Errors detected by a consistency check are automatically repaired. If false, errors are only written to the log. | 1.9
query-class | QueryImpl | Class name that implements the javax.jcr.query.Query interface.This class must also extend from the class: org.exoplatform.services.jcr.impl.core.query.AbstractQueryImpl. | 1.9
document-order | true | If true and the query does not contain an 'order by' clause, result nodes will be in document order. For better performance when queries return a lot of nodes set to 'false'. | 1.9
result-fetch-size | Integer.MAX_VALUE | The number of results when a query is executed. Default value: Integer.MAX_VALUE (-> all). | 1.9
excerptprovider-class | DefaultXMLExcerpt | The name of the class that implements org.exoplatform.services.jcr.impl.core.query.lucene.ExcerptProvider and should be used for the rep:excerpt() function in a query. | 1.9
support-highlighting | false | If set to true additional information is stored in the index to support highlighting using the rep:excerpt() function. | 1.9
synonymprovider-class | none | The name of a class that implements org.exoplatform.services.jcr.impl.core.query.lucene.SynonymProvider. The default value is null (-> not set). | 1.9
synonymprovider-config-path | none | The path to the synonym provider configuration file. This path interpreted is relative to the path parameter. If there is a path element inside the SearchIndex element, then this path is interpreted and relative to the root path of the path. Whether this parameter is mandatory or not, it depends on the synonym provider implementation. The default value is null (-> not set). | 1.9
indexing-configuration-path | none | The path to the indexing configuration file. | 1.9
indexing-configuration-class | IndexingConfigurationImpl | The name of the class that implements org.exoplatform.services.jcr.impl.core.query.lucene.IndexingConfiguration. | 1.9
force-consistencycheck | false | If setting to true, a consistency check is performed, depending on the parameter forceConsistencyCheck. If setting to false, no consistency check is performed on startup, even if a redo log had been applied. | 1.9
spellchecker-class | none | The name of a class that implements org.exoplatform.services.jcr.impl.core.query.lucene.SpellChecker. | 1.9
spellchecker-more-popular | true | If setting true, spellchecker returns only the suggest words that are as frequent or more frequent than the checked word. If setting false, spellchecker returns null (if checked word exit in dictionary), or spellchecker will return most close suggest word. | 1.10
spellchecker-min-distance | 0.55f | Minimal distance between checked word and proposed suggest word. | 1.10
errorlog-size | 50(Kb) | The default size of error log file in Kb. | 1.9
upgrade-index | false | Allows JCR to convert an existing index into the new format. Also, it is possible to set this property via system property, for example: -Dupgrade-index=true Indexes before JCR 1.12 will not run with JCR 1.12. Hence you have to run an automatic migration: Start JCR with -Dupgrade-index=true. The old index format is then converted in the new index format. After the conversion the new format is used. On the next start, you don't need this option anymore. The old index is replaced and a back conversion is not possible - therefore better take a backup of the index before. (Only for migrations from JCR 1.9 and later.) | 1.12
analyzer | org.apache.lucene.analysis.standard.StandardAnalyzer | Class name of a lucene analyzer to use for fulltext indexing of text. | 1.12

### 1.16.3. Global Search Index

#### Global Search Index Configuration

The global search index is configured in the above-mentioned configuration file (`portal/WEB-INF/conf/jcr/repository-configuration.xml`) in the tag "query-handler".

    <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

In fact, when using Lucene, you should always use the same analyzer for indexing and for querying, otherwise the results are unpredictable. You don't have to worry about this, eXo JCR does this for you automatically. If you don't like the StandardAnalyzer configured by default, just replace it by your own.

If you don't have a handy QueryHandler, you should learn how to create a customized Handler in 5 minutes.

#### Customized Search Indexes and Analyzers

By default Exo JCR uses the Lucene standard Analyzer to index contents. This analyzer uses some standard filters in the method that analyzes the content:

    public TokenStream tokenStream(String fieldName, Reader reader) {

        StandardTokenizer tokenStream = new StandardTokenizer(reader, replaceInvalidAcronym);

        tokenStream.setMaxTokenLength(maxTokenLength);

        TokenStream result = new StandardFilter(tokenStream);

        result = new LowerCaseFilter(result);

        result = new StopFilter(result, stopSet);

        return result;

      }

- The first one (StandardFilter) removes 's (as 's in "Peter's") from the end of words and removes dots from acronyms.

- The second one (LowerCaseFilter) normalizes token text to lower case.

- The last one (StopFilter) removes stop words from a token stream. The stop set is defined in the analyzer.

For specific cases, you may wish to use additional filters like ISOLatin1AccentFilter, which replaces accented characters in the ISO Latin 1 character set (ISO-8859-1) by their unaccented equivalents.

In order to use a different filter, you have to create a new analyzer, and a new search index to use the analyzer. You put it in a jar, which is deployed with your application.

##### Creating the filter

The ISOLatin1AccentFilter is not present in the current Lucene version used by eXo. You can use the attached file. You can also create your own filter, the relevant method is

    public final Token next(final Token reusableToken) throws java.io.IOException

which defines how chars are read and used by the filter.

##### Creating the analyzer

The analyzer has to extends org.apache.lucene.analysis.standard.StandardAnalyzer, and overload the method

    public TokenStream tokenStream(String fieldName, Reader reader)

to put your own filters. You can have a glance at the example analyzer attached to this article.

##### Creating the search index

Now, we have the analyzer, we have to write the SearchIndex, which will use the analyzer. Your have to extends org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex. You have to write the constructor, to set the right analyzer, and the method

    public Analyzer getAnalyzer() {

        return MyAnalyzer;

      }

to return your analyzer. You can see the attached SearchIndex.

### Note

Since 1.12 version, we can set Analyzer directly in configuration. So, creation new SearchIndex only for new Analyzer is redundant.

##### Configuring your application to use your SearchIndex

In `portal/WEB-INF/conf/jcr/repository-configuration.xml`, you have to replace each

    <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

by your own class

    <query-handler class="mypackage.indexation.MySearchIndex">

##### Configure your application to use your Analyzer

In `portal/WEB-INF/conf/jcr/repository-configuration.xml`, you have to add parameter "analyzer" to each query-handler config:

    <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

       <properties>

          ...

          <property name="analyzer" value="org.exoplatform.services.jcr.impl.core.MyAnalyzer"/>

          ...

       </properties>

    </query-handler>

When you start exo, your SearchIndex will start to index contents with the specified filters.

### 1.16.4. Indexing Adjustments

#### IndexingConfiguration

Starting with version 1.9, the default search index implementation in JCR allows you to control which properties of a node are indexed. You also can define different analyzers for different nodes.

The configuration parameter is called indexingConfiguration and per default is not set. This means all properties of a node are indexed.

If you wish to configure the indexing behavior, you need to add a parameter to the query-handler element in your configuration file.

    <param name="indexing-configuration-path" value="/indexing_configuration.xml"/>

#### Indexing rules

##### Node Scope Limit

To optimize the index size, you can limit the node scope so that only certain properties of a node type are indexed.

With the below configuration, only properties named Text are indexed for nodes of type nt:unstructured. This configuration also applies to all nodes whose type extends from nt:unstructured.

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <index-rule nodeType="nt:unstructured">

        <property>Text</property>

      </index-rule>

    </configuration>

Please note that you have to declare the namespace prefixes in the configuration element that you are using throughout the XML file!

##### Indexing Boost Value

It is also possible to configure a boost value for the nodes that match the index rule. The default boost value is 1.0. Higher boost values (a reasonable range is 1.0 - 5.0) will yield a higher score value and appear as more relevant.

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <index-rule nodeType="nt:unstructured"

                  boost="2.0">

        <property>Text</property>

      </index-rule>

    </configuration>

If you do not wish to boost the complete node but only certain properties, you can also provide a boost value for the listed properties:

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <index-rule nodeType="nt:unstructured">

        <property boost="3.0">Title</property>

        <property boost="1.5">Text</property>

      </index-rule>

    </configuration>

##### Conditional Index Rules

You may also add a condition to the index rule and have multiple rules with the same nodeType. The first index rule that matches will apply and all remain ones are ignored:

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <index-rule nodeType="nt:unstructured"

                  boost="2.0"

                  condition="@priority = 'high'">

        <property>Text</property>

      </index-rule>

      <index-rule nodeType="nt:unstructured">

        <property>Text</property>

      </index-rule>

    </configuration>

In the above example, the first rule only applies if the nt:unstructured node has a priority property with a value 'high'. The condition syntax supports only the equals operator and a string literal.

You may also refer properties in the condition that are not on the current node:

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <index-rule nodeType="nt:unstructured"

                  boost="2.0"

                  condition="ancestor::*/@priority = 'high'">

        <property>Text</property>

      </index-rule>

      <index-rule nodeType="nt:unstructured"

                  boost="0.5"

                  condition="parent::foo/@priority = 'low'">

        <property>Text</property>

      </index-rule>

      <index-rule nodeType="nt:unstructured"

                  boost="1.5"

                  condition="bar/@priority = 'medium'">

        <property>Text</property>

      </index-rule>

      <index-rule nodeType="nt:unstructured">

        <property>Text</property>

      </index-rule>

    </configuration>

The indexing configuration also allows you to specify the type of a node in the condition. Please note however that the type match must be exact. It does not consider sub types of the specified node type.

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <index-rule nodeType="nt:unstructured"

                  boost="2.0"

                  condition="element(*, nt:unstructured)/@priority = 'high'">

        <property>Text</property>

      </index-rule>

    </configuration>

##### Exclusion from the Node Scope Index

Per default all configured properties are fulltext indexed if they are of type STRING and included in the node scope index. A node scope search finds normally all nodes of an index. That is, the select jcr:contains(., 'foo') returns all nodes that have a string property containing the word 'foo'. You can exclude explicitly a property from the node scope index:

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <index-rule nodeType="nt:unstructured">

        <property nodeScopeIndex="false">Text</property>

      </index-rule>

    </configuration>

#### Indexing Aggregates

Sometimes it is useful to include the contents of descendant nodes into a single node to easier search on content that is scattered across multiple nodes.

JCR allows you to define indexed aggregates, basing on relative path patterns and primary node types.

The following example creates an indexed aggregate on nt:file that includes the content of the jcr:content node:

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:jcr="http://www.jcp.org/jcr/1.0"

                   xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <aggregate primaryType="nt:file">

        <include>jcr:content</include>

      </aggregate>

    </configuration>

You can also restrict the included nodes to a certain type:

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:jcr="http://www.jcp.org/jcr/1.0"

                   xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <aggregate primaryType="nt:file">

        <include primaryType="nt:resource">jcr:content</include>

      </aggregate>

    </configuration>

You may also use the * to match all child nodes:

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:jcr="http://www.jcp.org/jcr/1.0"

                   xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <aggregate primaryType="nt:file">http://wiki.exoplatform.com/xwiki/bin/edit/JCR/Search+Configuration

        <include primaryType="nt:resource">*</include>

      </aggregate>

    </configuration>

If you wish to include nodes up to a certain depth below the current node, you can add multiple include elements. E.g. the nt:file node may contain a complete XML document under jcr:content:

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:jcr="http://www.jcp.org/jcr/1.0"

                   xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <aggregate primaryType="nt:file">

        <include>*</include>

        <include>*/*</include>

        <include>*/*/*</include>

      </aggregate>

    </configuration>

#### Property-Level Analyzers

##### Example

In this configuration section, you define how a property has to be analyzed. If there is an analyzer configuration for a property, this analyzer is used for indexing and searching of this property. For example:

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.0.dtd">

    <configuration xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

      <analyzers>

            <analyzer class="org.apache.lucene.analysis.KeywordAnalyzer">

                <property>mytext</property>

            </analyzer>

            <analyzer class="org.apache.lucene.analysis.WhitespaceAnalyzer">

                <property>mytext2</property>

            </analyzer>

      </analyzers>

    </configuration>

The configuration above means that the property "mytext" for the entire workspace is indexed (and searched) with the Lucene KeywordAnalyzer, and property "mytext2" with the WhitespaceAnalyzer. Using different analyzers for different languages is particularly useful.

The WhitespaceAnalyzer tokenizes a property, the KeywordAnalyzer takes the property as a whole.

##### Characteristics of Node Scope Searches

When using analyzers, you may encounter an unexpected behavior when searching within a property compared to searching within a node scope. The reason is that the node scope always uses the global analyzer.

Let's suppose that the property "mytext" contains the text : "testing my analyzers" and that you haven't configured any analyzers for the property "mytext" (and not changed the default analyzer in SearchIndex).

If your query is for example:

    xpath = "//*[jcr:contains(mytext,'analyzer')]"

This xpath does not return a hit in the node with the property above and default analyzers.

Also a search on the node scope

    xpath = "//*[jcr:contains(.,'analyzer')]"

won't give a hit. Realize that you can only set specific analyzers on a node property, and that the node scope indexing/analyzing is always done with the globally defined analyzer in the SearchIndex element.

Now, if you change the analyzer used to index the "mytext" property above to

    <analyzer class="org.apache.lucene.analysis.Analyzer.GermanAnalyzer">

         <property>mytext</property>

    </analyzer>

and you do the same search again, then for

    xpath = "//*[jcr:contains(mytext,'analyzer')]"

you would get a hit because of the word stemming (analyzers - analyzer).

The other search,

    xpath = "//*[jcr:contains(.,'analyzer')]"

still would not give a result, since the node scope is indexed with the global analyzer, which in this case does not take into account any word stemming.

In conclusion, be aware that when using analyzers for specific properties, you might find a hit in a property for some search text, and you do not find a hit with the same search text in the node scope of the property!

### Note

Both index rules and index aggregates influence how content is indexed in JCR. If you change the configuration, the existing content is not automatically re-indexed according to the new rules. You, therefore, have to manually re-index the content when you change the configuration!

#### Advanced features

eXo JCR supports some advanced features, which are not specified in JSR 170:

- Get a text excerpt with highlighted words that matches the query: ExcerptProvider.

- Search a term and its synonyms: SynonymSearch

- Search similar nodes: SimilaritySearch

- Check spelling of a full text query statement: SpellChecker

- Define index aggregates and rules: IndexingConfiguration.

## 1.17. JCR Configuration persister

eXo JCR allows using persister to store configuration. In this section, you will understand how to use and configure eXo JCR persister.

### 1.17.1. Idea

JCR Repository Service uses `org.exoplatform.services.jcr.config.RepositoryServiceConfiguration` component to read its configuration.

    <component>

        <key>org.exoplatform.services.jcr.config.RepositoryServiceConfiguration</key>

        <type>org.exoplatform.services.jcr.impl.config.RepositoryServiceConfigurationImpl</type>

        <init-params>

          <value-param>

            <name>conf-path</name>

            <description>JCR configuration file</description>

            <value>/conf/standalone/exo-jcr-config.xml</value>

          </value-param>

        </init-params>

      </component>

In the example, Repository Service will read the configuration from the file `/conf/standalone/exo-jcr-config.xml`.

But in some cases, it's required to change the configuration on the fly. And know that the new one will be used. Additionally we wish not to modify the original file.

In this case, we have to use the configuration persister feature which allows to store the configuration in different locations.

### 1.17.2. Usage

On startup `RepositoryServiceConfiguration` component checks if a configuration persister was configured. In that case, it uses the provided `ConfigurationPersister` implementation class to instantiate the persister object.

Configuration with persister:

    <component>

        <key>org.exoplatform.services.jcr.config.RepositoryServiceConfiguration</key>

        <type>org.exoplatform.services.jcr.impl.config.RepositoryServiceConfigurationImpl</type>

        <init-params>

          <value-param>

            <name>conf-path</name>

            <description>JCR configuration file</description>

            <value>/conf/standalone/exo-jcr-config.xml</value>

          </value-param>

          <properties-param>

            <name>working-conf</name>

            <description>working-conf</description>

            <property name="source-name" value="jdbcjcr" />

            <property name="dialect" value="mysql" />

            <property name="persister-class-name" value="org.exoplatform.services.jcr.impl.config.JDBCConfigurationPersister" />

          </properties-param>

        </init-params>

      </component>

Where:

- `source-name`: JNDI source name configured in `InitialContextInitializer` component. (`sourceName` prior v.1.9.) Find more in database configuration.

- `dialect`: SQL dialect which will be used with database from `source-name`. Find more in database configuration.

- `persister-class-name` - class name of `ConfigurationPersister` interface implementation. (`persisterClassName` prior v.1.9.)

ConfigurationPersister interface:

    /**

       * Init persister.

       * Used by RepositoryServiceConfiguration on init.

       * @return - config data stream

       */

      void init(PropertiesParam params) throws RepositoryConfigurationException;

      /**

       * Read config data.

       * @return - config data stream

       */

      InputStream read() throws RepositoryConfigurationException;

      /**

       * Create table, write data.

       * @param confData - config data stream

       */

      void write(InputStream confData) throws RepositoryConfigurationException;

      /**

       * Tell if the config exists.

       * @return - flag

       */

      boolean hasConfig() throws RepositoryConfigurationException;

JCR Core implementation contains a persister which stores the repository configuration in the relational database using JDBC calls - `org.exoplatform.services.jcr.impl.config.JDBCConfigurationPersister`.

The implementation will crate and use table JCR_CONFIG in the provided database.

But the developer can implement his own persister for his particular usecase.

## 1.18. JDBC Data Container Config

eXo JCR persistent data container can work in two configuration modes:

- Multi-database: One database for each workspace (used in standalone eXo JCR service mode)

- Single-database: All workspaces persisted in one database (used in embedded eXo JCR service mode, e.g. in eXo portal)

The data container uses the JDBC driver to communicate with the actual database software, i.e. any JDBC-enabled data storage can be used with eXo JCR implementation.

Currently the data container is tested with the following configurations:

- MySQL 5.0.18 MYSQL Connector/J 5.0.8

- MySQL 5.1.36 MYSQL Connector/J 5.1.14

- PostgresSQL 8.2.4 JDBC4 Driver, Version 8.2-507

- PostgresSQL 8.3.7 JDBC4 Driver, Version 8.3-606

- Oracle DB 10g R2 (10.2.0.4), JDBC Driver Oracle 10g R2 (10.2.0.4)

- Oracle DB 11g R1 (11.1.0.6.0), JDBC Driver Oracle 11g R1 (11.1.0.6.0)

- Oracle DB 11g R2 (11.2.0.1.0), JDBC Driver Oracle 11g R2 (11.2.0.1.0)

- DB2 9.7.4 IBM Data Server Driver for JDBC and SQLJ (JCC Driver) v.9.7

- MS SQL Server 2005 SP3 JDBC Driver 3.0

- MS SQL Server 2008 JDBC Driver 3.0

- Sybase 15.0.3 ASE Driver: Sybase jConnect JDBC driver v7 (Build 26502)

- HSQLDB (2.0.0)

Each database software supports ANSI SQL standards but also has its own specifics. So, each database has its own configuration in eXo JCR as a database dialect parameter. If you need a more detailed configuration of the database, it's possible to do that by editing the metadata SQL-script files.

SQL-scripts you can obtain from jar-file exo.jcr.component.core-XXX.XXX.jar:conf/storage/. They also can be found at SVN here.

In the next two tables correspondence between the scripts and databases is shown.

MySQL DB | jcr-sjdbc.mysql.sql
MySQL DB with utf-8 | jcr-sjdbc.mysql-utf8.sql
MySQL DB with MyISAM* | jcr-sjdbc.mysql-myisam.sql
MySQL DB with MyISAM and utf-8* | jcr-sjdbc.mysql-myisam-utf8.sql
PostgresSQL | jcr-sjdbc.pqsql.sql
Oracle DB | jcr-sjdbc.ora.sql
DB2 | jcr-sjdbc.db2.sql
MS SQL Server | jcr-sjdbc.mssql.sql
Sybase | jcr-sjdbc.sybase.sql
HSQLDB | jcr-sjdbc.sql

MySQL DB | jcr-mjdbc.mysql.sql
MySQL DB with utf-8 | jcr-mjdbc.mysql-utf8.sql
MySQL DB with MyISAM* | jcr-mjdbc.mysql-myisam.sql
MySQL DB with MyISAM and utf-8* | jcr-mjdbc.mysql-myisam-utf8.sql
PostgresSQL | jcr-mjdbc.pqsql.sql
Oracle DB | jcr-mjdbc.ora.sql
DB2 | jcr-mjdbc.db2.sql
MS SQL Server | jcr-mjdbc.mssql.sql
Sybase | jcr-mjdbc.sybase.sql
HSQLDB | jcr-mjdbc.sql

In case the non-ANSI node name is used, it's necessary to use a database with MultiLanguage support[TODO link to MultiLanguage]. Some JDBC drivers need additional parameters for establishing a Unicode friendly connection. E.g. under mysql it's necessary to add an additional parameter for the JDBC driver at the end of JDBC URL. For instance: `jdbc:mysql://exoua.dnsalias.net/portal?characterEncoding=utf8`

There are preconfigured configuration files for HSQLDB. Look for these files in /conf/portal and /conf/standalone folders of the jar-file exo.jcr.component.core-XXX.XXX.jar or source-distribution of eXo JCR implementation.

By default, the configuration files are located in service jars `/conf/portal/configuration.xml` (eXo services including JCR Repository Service) and `exo-jcr-config.xml` (repositories configuration). In eXo portal product, JCR is configured in portal web application `portal/WEB-INF/conf/jcr/jcr-configuration.xml` (JCR Repository Service and related serivces) and repository-configuration.xml (repositories configuration).

Read more about Repository configuration.

### 1.18.1. General recommendations for database configuration

- Please note, that JCR requires at least READ_COMMITED isolation level and other RDBMS configurations can cause some side-effects and issues. So, please, make sure proper isolation level is configured on database server side.

#### DB2 configuration

- RDBMS reindexing feature use queries based on LIMIT and OFFSET clauses which are not enabled by default. However, you can ensure they are enabled by executing the following

      $ db2set DB2_COMPATIBILITY_VECTOR=MYS
      $ db2stop
      $ db2start

- Statistics is collected automatically starting from DB2 Version 9, however it is needed to launch statistics collection manually during the very first start, otherwise it could be very long. You need to run 'RUNSTATS' command

      RUNSTATS ON TABLE <scheme>.<table> WITH DISTRIBUTION AND INDEXES ALL

  for JCR_SITEM (or JCR_MITEM) and JCR_SVALUE (or JCR_MVALUE) tables.

#### MySQL configuration

- To prevent any consistency issues please ensure that InnoDB is configured as default MySQL engine (instead of MyISAM by default) before launching your application for the very first time otherwise when the application will create the tables MyISAM will be used as MySQL engine which is a non transactional engine that doesn't not support integrity constraints so even if later you swicth to InnoDB using an alter table all the integrity constraints would be missing as they would have been removed at table creation table by MyISAM.

- MyISAM is not supported due to its lack of transaction support and integrity check, so use it only if you don't expect any support and if performances in read accesses are more important than the consistency in your use-case. Therefore the dialects mysql-myisam and mysql-myisam-utf8 are only dedicated to the community.

- MySQL relies on collected statistics for keeping track of data distribution in tables and for optimizing join statements, but you can manually call 'ANALYZE' to update statistics if needed. For example

      ANALYZE TABLE JCR_SITEM, JCR_SVALUE

#### PostgreSQL configuration

- Be aware, when using RDBMS for reindexing need to set "enable_seqscan" to "off" or "default_statistics_target" at least "50"

- Though PostgreSQL server performs query optimization automatically, you can manualy call 'ANALYZE' command to collect statistics which can influence the performance. For example

      ANALYZE JCR_SITEM
      ANALYZE JCR_SVALUE

#### MS SQL configuration

- One more mandatory JCR requirement for underlying databases is a case sensitive collation. Microsoft SQL Server both 2005 and 2008 customers must configure their server with collation corresponding to personal needs and requirements, but obligatorily case sensitive. For more information please refer to Microsoft SQL Server documentation page "Selecting a SQL Server Collation" here.

- MS SQL DB server's optimizer automatically processes queries to increase performance. Optimization is based on statistical data which is collected automatically, but you can manually call Transact-SQL command 'UPDATE STATISTICS' which in very few situations may increase performance. For example

      UPDATE STATISTICS JCR_SITEM
      UPDATE STATISTICS JCR_SVALUE

#### Sybase configuration

- Sybase DB Server optimizer automatically processes queries to increase performance. Optimization is based on statistical data which is collected automatically, but you can manually call Transact-SQL command 'update statistics' which in very few situations may increase performance. For example

      update statistics JCR_SITEM
      update statistics JCR_SVALUE

#### Oracle configuration

- Oracle DB automatically collects statistics to optimize performance of queries, but you can manually call 'ANALYZE' command to start collecting statistics immediately which may improve performance. For example

      ANALYZE INDEX JCR_PK_SITEM COMPUTE STATISTICS
      ANALYZE INDEX JCR_IDX_SITEM_PARENT_FK COMPUTE STATISTICS
      ANALYZE INDEX JCR_IDX_SITEM_PARENT COMPUTE STATISTICS
      ANALYZE INDEX JCR_IDX_SITEM_PARENT_NAME COMPUTE STATISTICS
      ANALYZE INDEX JCR_IDX_SITEM_PARENT_ID COMPUTE STATISTICS
      ANALYZE INDEX JCR_PK_SVALUE COMPUTE STATISTICS
      ANALYZE INDEX JCR_IDX_SVALUE_PROPERTY COMPUTE STATISTICS
      ANALYZE INDEX JCR_PK_SREF COMPUTE STATISTICS
      ANALYZE INDEX JCR_IDX_SREF_PROPERTY COMPUTE STATISTICS
      ANALYZE INDEX JCR_PK_SCONTAINER COMPUTE STATISTICS

### 1.18.2. Multi-database Configuration

You need to configure each workspace in a repository. You may have each one on different remote servers as far as you need.

First of all configure the data containers in the `org.exoplatform.services.naming.InitialContextInitializer` service. It's the JNDI context initializer which registers (binds) naming resources (DataSources) for data containers.

For example (standalone mode, two data containers `jdbcjcr` - local HSQLDB, `jdbcjcr1` - remote MySQL):

    <component>

        <key>org.exoplatform.services.naming.InitialContextInitializer</key>

        <type>org.exoplatform.services.naming.InitialContextInitializer</type>

        <component-plugins>

          <component-plugin>

            <name>bind.datasource</name>

            <set-method>addPlugin</set-method>

            <type>org.exoplatform.services.naming.BindReferencePlugin</type>

            <init-params>

              <value-param>

                <name>bind-name</name>

                <value>jdbcjcr</value>

              </value-param>

              <value-param>

                <name>class-name</name>

                <value>javax.sql.DataSource</value>

              </value-param>

              <value-param>

                <name>factory</name>

                <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>

              </value-param>

              <properties-param>

                <name>ref-addresses</name>

                <description>ref-addresses</description>

                <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>

                <property name="url" value="jdbc:hsqldb:file:target/temp/data/portal"/>

                <property name="username" value="sa"/>

                <property name="password" value=""/>

              </properties-param>

            </init-params>

          </component-plugin>

          <component-plugin>

            <name>bind.datasource</name>

            <set-method>addPlugin</set-method>

            <type>org.exoplatform.services.naming.BindReferencePlugin</type>

            <init-params>

              <value-param>

                <name>bind-name</name>

                <value>jdbcjcr1</value>

              </value-param>

              <value-param>

                <name>class-name</name>

                <value>javax.sql.DataSource</value>

              </value-param>

              <value-param>

                <name>factory</name>

                <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>

              </value-param>

              <properties-param>

                <name>ref-addresses</name>

                <description>ref-addresses</description>

                <property name="driverClassName" value="com.mysql.jdbc.Driver"/>

                <property name="url" value="jdbc:mysql://exoua.dnsalias.net/jcr"/>

                <property name="username" value="exoadmin"/>

                <property name="password" value="exo12321"/>

                <property name="maxActive" value="50"/>

                <property name="maxIdle" value="5"/>

                <property name="initialSize" value="5"/>

              </properties-param>

            </init-params>

          </component-plugin>

        <component-plugins>

        <init-params>

          <value-param>

            <name>default-context-factory</name>

            <value>org.exoplatform.services.naming.SimpleContextFactory</value>

          </value-param>

        </init-params>

      </component>

We configure the database connection parameters:

- `driverClassName`, e.g. "org.hsqldb.jdbcDriver", "com.mysql.jdbc.Driver", "org.postgresql.Driver"

- `url`, e.g. "jdbc:hsqldb:file:target/temp/data/portal", "jdbc:mysql://exoua.dnsalias.net/jcr"

- `username`, e.g. "sa", "exoadmin"

- `password`, e.g. "", "exo12321"

There can be connection pool configuration parameters (org.apache.commons.dbcp.BasicDataSourceFactory):

- `maxActive`, e.g. 50

- `maxIdle`, e.g. 5

- `initialSize`, e.g. 5

- and other according to Apache DBCP configuration

When the data container configuration is done, we can configure the repository service. Each workspace will be configured for its own data container.

For example (two workspaces `ws` - jdbcjcr, `ws1` - jdbcjcr1):

    <workspaces>

      <workspace name="ws" auto-init-root-nodetype="nt:unstructured">

        <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

        <properties>

          <property name="source-name" value="jdbcjcr"/>

          <property name="dialect" value="hsqldb"/>

          <property name="multi-db" value="true"/>

          <property name="max-buffer-size" value="200K"/>

          <property name="swap-directory" value="target/temp/swap/ws"/>

        </properties>

        </container>

        <cache enabled="true">

          <properties>

            <property name="max-size" value="10K"/><!-- 10Kbytes -->

            <property name="live-time" value="30m"/><!-- 30 min -->

          </properties>

        </cache>

        <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

        <properties>

          <property name="index-dir" value="target/temp/index"/>

        </properties>

        </query-handler>

        <lock-manager>

        <time-out>15m</time-out><!-- 15 min -->

        <persister class="org.exoplatform.services.jcr.impl.core.lock.FileSystemLockPersister">

          <properties>

          <property name="path" value="target/temp/lock/ws"/>

          </properties>

        </persister>

        </lock-manager>

      </workspace>

      <workspace name="ws1" auto-init-root-nodetype="nt:unstructured">

        <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

        <properties>

          <property name="source-name" value="jdbcjcr1"/>

          <property name="dialect" value="mysql"/>

          <property name="multi-db" value="true"/>

          <property name="max-buffer-size" value="200K"/>

          <property name="swap-directory" value="target/temp/swap/ws1"/>

        </properties>

        </container>

        <cache enabled="true">

          <properties>

            <property name="max-size" value="10K"/>

            <property name="live-time" value="5m"/>

          </properties>

        </cache>

        <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

        <properties>

          <property name="index-dir" value="target/temp/index"/>

        </properties>

        </query-handler>

        <lock-manager>

        <time-out>15m</time-out><!-- 15 min -->

        <persister class="org.exoplatform.services.jcr.impl.core.lock.FileSystemLockPersister">

          <properties>

          <property name="path" value="target/temp/lock/ws1"/>

          </properties>

        </persister>

        </lock-manager>

      </workspace>

    </workspaces>

- `source-name`: A javax.sql.DataSource name configured in InitialContextInitializer component (was `sourceName` prior JCR 1.9);

- `dialect`: A database dialect, one of "hsqldb", "mysql", "mysql-utf8", "pgsql", "oracle", "oracle-oci", "mssql", "sybase", "derby", "db2", "db2v8" or "auto" for dialect autodetection;

- `multi-db`: Enable multi-database container with this parameter (set value "true");

- `max-buffer-size: A` a threshold (in bytes) after which a javax.jcr.Value content will be swapped to a file in a temporary storage. I.e. swap for pending changes.

- `swap-directory`: A path in the file system used to swap the pending changes.

In this way, we have configured two workspace which will be persisted in two different databases (ws in HSQLDB, ws1 in MySQL).

### Note

Starting from v.1.9 repository configuration parameters supports human-readable formats of values (e.g. 200K - 200 Kbytes, 30m - 30 minutes etc)

### 1.18.3. Single-database configuration

It's more simple to configure a single-database data container. We have to configure one naming resource.

For example (embedded mode for `jdbcjcr` data container):

    <external-component-plugins>

        <target-component>org.exoplatform.services.naming.InitialContextInitializer</target-component>

        <component-plugin>

            <name>bind.datasource</name>

            <set-method>addPlugin</set-method>

            <type>org.exoplatform.services.naming.BindReferencePlugin</type>

            <init-params>

              <value-param>

                <name>bind-name</name>

                <value>jdbcjcr</value>

              </value-param>

              <value-param>

                <name>class-name</name>

                <value>javax.sql.DataSource</value>

              </value-param>

              <value-param>

                <name>factory</name>

                <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>

              </value-param>

              <properties-param>

                <name>ref-addresses</name>

                <description>ref-addresses</description>

                <property name="driverClassName" value="org.postgresql.Driver"/>

                <property name="url" value="jdbc:postgresql://exoua.dnsalias.net/portal"/>

                <property name="username" value="exoadmin"/>

                <property name="password" value="exo12321"/>

                <property name="maxActive" value="50"/>

                <property name="maxIdle" value="5"/>

                <property name="initialSize" value="5"/>

              </properties-param>

            </init-params>

        </component-plugin>

      </external-component-plugins>

And configure repository workspaces in repositories configuration with this one database. Parameter "multi-db" must be switched off (set value "false").

For example (two workspaces `ws` - jdbcjcr, `ws1` - jdbcjcr):

    <workspaces>

      <workspace name="ws" auto-init-root-nodetype="nt:unstructured">

        <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

        <properties>

          <property name="source-name" value="jdbcjcr"/>

          <property name="dialect" value="pgsql"/>

          <property name="multi-db" value="false"/>

          <property name="max-buffer-size" value="200K"/>

          <property name="swap-directory" value="target/temp/swap/ws"/>

        </properties>

        </container>

        <cache enabled="true">

        <properties>

          <property name="max-size" value="10K"/>

          <property name="live-time" value="30m"/>

        </properties>

        </cache>

        <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

        <properties>

          <property name="index-dir" value="../temp/index"/>

        </properties>

        </query-handler>

        <lock-manager>

        <time-out>15m</time-out>

        <persister class="org.exoplatform.services.jcr.impl.core.lock.FileSystemLockPersister">

          <properties>

          <property name="path" value="target/temp/lock/ws"/>

          </properties>

        </persister>

        </lock-manager>

      </workspace>

      <workspace name="ws1" auto-init-root-nodetype="nt:unstructured">

        <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

        <properties>

          <property name="source-name" value="jdbcjcr"/>

          <property name="dialect" value="pgsql"/>

          <property name="multi-db" value="false"/>

          <property name="max-buffer-size" value="200K"/>

          <property name="swap-directory" value="target/temp/swap/ws1"/>

        </properties>

        </container>

        <cache enabled="true">

        <properties>

          <property name="max-size" value="10K"/>

          <property name="live-time" value="5m"/>

        </properties>

        </cache>

        <lock-manager>

        <time-out>15m</time-out>

        <persister class="org.exoplatform.services.jcr.impl.core.lock.FileSystemLockPersister">

          <properties>

          <property name="path" value="target/temp/lock/ws1"/>

          </properties>

        </persister>

        </lock-manager>

      </workspace>

    </workspaces>

In this way, we have configured two workspaces which will be persisted in one database (PostgreSQL).

#### Configuration without DataSource

Repository configuration without using of the `javax.sql.DataSource` bounded in JNDI.

This case may be usable if you have a dedicated JDBC driver implementation with special features like XA transactions, statements/connections pooling etc:

- You have to remove the configuration in `InitialContextInitializer` for your database and configure a new one directly in the workspace container.

- Remove parameter "source-name" and add next lines instead. Describe your values for a JDBC driver, database url and username.

### Note

But be careful in this case JDBC driver should implement and provide connection pooling. Connection pooling is very recommended for use with JCR to prevent a database overload.

    <workspace name="ws" auto-init-root-nodetype="nt:unstructured">

      <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

        <properties>

          <property name="dialect" value="hsqldb"/>

          <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>

          <property name="url" value="jdbc:hsqldb:file:target/temp/data/portal"/>

          <property name="username" value="su"/>

          <property name="password" value=""/>

          ......

#### Dynamic Workspace Creation

Workspaces can be added dynamically during runtime.

This can be performed in two steps:

- Firstly, `ManageableRepository.configWorkspace(WorkspaceEntry wsConfig)` - register a new configuration in RepositoryContainer and create a WorkspaceContainer.

- Secondly, the main step, `ManageableRepository.createWorkspace(String workspaceName)` - creation of a new workspace.

### 1.18.4. Simple and Complex queries

eXo JCR provides two ways for interact with Database - `JDBCStorageConnection` that uses simple queries and `CQJDBCStorageConection` that uses complex queries for reducing amount of database callings.

Simple queries will be used if you chose `org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer`:

    <workspaces>

      <workspace name="ws" auto-init-root-nodetype="nt:unstructured">

        <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

        ...

      </workspace>

    </worksapces>

Complex queries will be used if you chose `org.exoplatform.services.jcr.impl.storage.jdbc.optimisation.CQJDBCWorkspaceDataContainer`:

    <workspaces>

      <workspace name="ws" auto-init-root-nodetype="nt:unstructured">

        <container class="org.exoplatform.services.jcr.impl.storage.jdbc.optimisation.CQJDBCWorkspaceDataContainer">

        ...

      </workspace>

    </worksapces>

Why we should use a Complex Queries?

They are optimised to reduce amount of requests to database.

Why we should use a Simple Queries?

Simple queries implemented in way to support as many database dialects as possible.
Simple queries do not use sub queries, left or right joins.

### 1.18.5. Forse Query Hints

Some databases supports hints to increase query performance (like Oracle, MySQL, etc). eXo JCR have separate Complex Query implementation for Orcale dialect, that uses query hints to increase performance for few important queries.

To enable this option put next configuration property:

    <workspace name="ws" auto-init-root-nodetype="nt:unstructured">

      <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

        <properties>

          <property name="dialect" value="oracle"/>

          <property name="force.query.hints" value="true" />

          ......

Query hints enabled by default.

eXo JCR uses query hints only for Complex Query Oracle dialect. For all other dialects this parameter is ignored.

### 1.18.6. Notes for Microsoft Windows users

The current configuration of eXo JCR uses Apache DBCP connection pool (`org.apache.commons.dbcp.BasicDataSourceFactory`). It's possible to set a big value for maxActive parameter in `configuration.xml`. That means usage of lots of TCP/IP ports from a client machine inside the pool (i.e. JDBC driver). As a result, the data container can throw exceptions like "Address already in use". To solve this problem, you have to configure the client's machine networking software for the usage of shorter timeouts for opened TCP/IP ports.

Microsoft Windows has `MaxUserPort`, `TcpTimedWaitDelay` registry keys in the node `HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesTcpipParameters`, by default these keys are unset, set each one with values like these:

- "TcpTimedWaitDelay"=dword:0000001e, sets TIME_WAIT parameter to 30 seconds, default is 240.

- "MaxUserPort"=dword:00001b58, sets the maximum of open ports to 7000 or higher, default is 5000.

A sample registry file is below:

    Windows Registry Editor Version 5.00

    [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]
    "MaxUserPort"=dword:00001b58
    "TcpTimedWaitDelay"=dword:0000001e

## 1.19. External Value Storages

By default JCR Values are stored in the Workspace Data container along with the JCR structure (i.e. Nodes and Properties). eXo JCR offers an additional option of storing JCR Values separately from Workspace Data container, which can be extremely helpful to keep Binary Large Objects (BLOBs) for example.

Value storage configuration is a part of Repository configuration, find more details there.

Tree-based storage is recommended for most of cases. If you run an application on Amazon EC2 - the S3 option may be interesting for architecture. Simple 'flat' storage is good in speed of creation/deletion of values, it might be a compromise for a small storages.

### 1.19.1. Tree File Value Storage

Holds Values in tree-like FileSystem files. path property points to the root directory to store the files.

This is a recommended type of external storage, it can contain large amount of files limited only by disk/volume free space.

A disadvantage is that it's a higher time on Value deletion due to unused tree-nodes remove.

    <value-storage id="Storage #1" class="org.exoplatform.services.jcr.impl.storage.value.fs.TreeFileValueStorage">

         <properties>

           <property name="path" value="data/values"/>

         </properties>

         <filters>

           <filter property-type="Binary" min-value-size="1M"/>

         </filters>

Where :

`id`: The value storage unique identifier, used for linking with properties stored in workspace container.
`path`: A location where value files will be stored.

Each file value storage can have the `filter(s)` for incoming values. A filter can match values by property type (property-type), property name (property-name), ancestor path (ancestor-path) and/or size of values stored (min-value-size, in bytes). In code sample, we use a filter with property-type and min-value-size only. I.e. storage for binary values with size greater of 1MB. It's recommended to store properties with large values in file value storage only.

Another example shows a value storage with different locations for large files (min-value-size a 20Mb-sized filter). A value storage uses ORed logic in the process of filter selection. That means the first filter in the list will be asked first and if not matched the next will be called etc. Here a value matches the 20 MB-sized filter min-value-size and will be stored in the path "data/20Mvalues", all other in "data/values".

    <value-storages>

      <value-storage id="Storage #1" class="org.exoplatform.services.jcr.impl.storage.value.fs.TreeFileValueStorage">

        <properties>

          <property name="path" value="data/20Mvalues"/>

        </properties>

        <filters>

          <filter property-type="Binary" min-value-size="20M"/>

        </filters>

      <value-storage>

      <value-storage id="Storage #2" class="org.exoplatform.services.jcr.impl.storage.value.fs.TreeFileValueStorage">

        <properties>

          <property name="path" value="data/values"/>

        </properties>

        <filters>

          <filter property-type="Binary" min-value-size="1M"/>

        </filters>

      <value-storage>

    <value-storages>

### 1.19.2. Simple File Value Storage

### Note

It's not recommended to use in production due to low capacity capabilities on most file systems.

But if you're sure in your file-system or data amount is small it may be useful for you as haves a faster speed of Value removal.

Hold Values in flat FileSystem files. path property points to root directory in order to store files

    <value-storage id="Storage #1" class="org.exoplatform.services.jcr.impl.storage.value.fs.SimpleFileValueStorage">

         <properties>

           <property name="path" value="data/values"/>

         </properties>

         <filters>

           <filter property-type="Binary" min-value-size="1M"/>

         </filters>

### 1.19.3. Content Addressable Value storage (CAS) support

eXo JCR supports Content-addressable storage feature for Values storing.

### Note

Content-addressable storage, also referred to as associative storage and abbreviated CAS, is a mechanism for storing information that can be retrieved based on its content, not its storage location. It is typically used for high-speed storage and retrieval of fixed content, such as documents stored for compliance with government regulations.

Content Addressable Value storage stores unique content once. Different properties (values) with same content will be stored as one data file shared between those values. We can tell the Value content will be shared across some Values in storage and will be stored on one physical file.

Storage size will be decreased for application which governs potentially same data in the content.

### Note

For example: if you have 100 different properties containing the same data (e.g. mail attachment), the storage stores only one single file. The file will be shared with all referencing properties.

If property Value changes, it is stored in an additional file. Alternatively the file is shared with other values, pointing to the same content.

The storage calculates Value content address each time the property was changed. CAS write operations are much more expensive compared to the non-CAS storages.

Content address calculation based on java.security.MessageDigest hash computation and tested with MD5 and SHA1 algorithms.

### Note

CAS storage works most efficiently on data that does not change often. For data that changes frequently, CAS is not as efficient as location-based addressing.

CAS support can be enabled for Tree and Simple File Value Storage types.

To enable CAS support, just configure it in JCR Repositories configuration as we do for other Value Storages.

    <workspaces>

            <workspace name="ws">

              <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

                <properties>

                  <property name="source-name" value="jdbcjcr"/>

                  <property name="dialect" value="oracle"/>

                  <property name="multi-db" value="false"/>

                  <property name="update-storage" value="false"/>

                  <property name="max-buffer-size" value="200k"/>

                  <property name="swap-directory" value="target/temp/swap/ws"/>

                </properties>

                <value-storages>

    <!------------------- here ----------------------->

                  <value-storage id="ws" class="org.exoplatform.services.jcr.impl.storage.value.fs.CASableTreeFileValueStorage">

                    <properties>

                      <property name="path" value="target/temp/values/ws"/>

                      <property name="digest-algo" value="MD5"/>

                      <property name="vcas-type" value="org.exoplatform.services.jcr.impl.storage.value.cas.JDBCValueContentAddressStorageImpl"/>

                      <property name="jdbc-source-name" value="jdbcjcr"/>

                      <property name="jdbc-dialect" value="oracle"/>

                    </properties>

                    <filters>

                      <filter property-type="Binary"/>

                    </filters>

                  </value-storage>

                </value-storages>

Properties:

`digest-algo`: Digest hash algorithm (MD5 and SHA1 were tested);
`vcas-type`: Value CAS internal data type, JDBC backed is currently implemented org.exoplatform.services.jcr.impl.storage.value.cas.JDBCValueContentAddressStorageImp;l
`jdbc-source-name`: JDBCValueContentAddressStorageImpl specific parameter, database will be used to save CAS metadata. It's simple to use same as in workspace container;
`jdbc-dialect`: JDBCValueContentAddressStorageImpl specific parameter, database dialect. It's simple to use the same as in workspace container;

### 1.19.4. Disabling value storage

JCR allows to disable value storage by adding property into configuration. For interal usage and testing purpose only.

    <property name="enabled" value="false" />

Be careful, all stored values will be unaccessible.

## 1.20. Workspace Data Container

Each Workspace of JCR has its own persistent storage to hold workspace's items data. eXo Content Repository can be configured so that it can use one or more workspaces that are logical units of the repository content. Physical data storage mechanism is configured using mandatory element container. The type of container is described in the attribute class = fully qualified name of org.exoplatform.services.jcr.storage.WorkspaceDataContainer subclass like

    <container class="org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer">

      <properties>

        <property name="source-name" value="jdbcjcr1"/>

        <property name="dialect" value="hsqldb"/>

        <property name="multi-db" value="true"/>

        <property name="max-buffer-size" value="200K"/>

        <property name="swap-directory" value="target/temp/swap/ws"/>

        <property name="lazy-node-iterator-page-size" value="50"/>

        <property name="acl-bloomfilter-false-positive-probability" value="0.1d"/>

        <property name="acl-bloomfilter-elements-number" value="1000000"/>

      </properties>

Properties are Container specific parameters:

source-name: JDBC data source name, registered in JDNI by InitialContextInitializer. ( sourceName prior v.1.9)

dialect: Database dialect, one of "hsqldb", "mysql", "mysql-utf8", "pgsql", "oracle", "oracle-oci", "mssql", "sybase", "derby", "db2", "db2v8"

multi-db: Enable multi-database container with this parameter (if "true").

max-buffer-size: A threshold in bytes, if a value size is greater, then it will be spooled to a temporary file.

swap-directory: A location where the value will be spooled if no value storage is configured but a max-buffer-size is exceeded.

lazy-node-iterator-page-size: "Lazy" child nodes iterator settings. Defines size of page, the number of nodes that are retrieved from persistent storage at once.

acl-bloomfilter-false-positive-probability: ACL Bloom-filter settings. ACL Bloom-filter desired false positive probability. Range [0..1]. Default value 0.1d.

acl-bloomfilter-elements-number: ACL Bloom-filter settings. Expected number of ACL-elements in the Bloom-filter. Default value 1000000.

### Note

Bloom filters are not supported by all the cache implementations so far only the inplementation for infinispan supports it.

Bloom-filter used to avoid read nodes that definitely do not have ACL. acl-bloomfilter-false-positive-probability and acl-bloomfilter-elements-number used to configure such filters. Bloom filters are not supported by all the cache implementations so far only the inplementation for infinispan supports it.

More about Bloom filters you can read here http://en.wikipedia.org/wiki/Bloom_filter.

eXo JCR has an RDB (JDBC) based, production ready Workspace Data Container.

Workspace Data Container MAY support external storages for javax.jcr.Value (which can be the case for BLOB values for example) using the optional element value-storages. Data Container will try to read or write Value using underlying value storage plugin if the filter criteria (see below) match the current property.

    <value-storages>

      <value-storage id="Storage #1" class="org.exoplatform.services.jcr.impl.storage.value.fs.TreeFileValueStorage">

        <properties>

          <property name="path" value="data/values"/>

        </properties>

        <filters>

         <filter property-type="Binary" min-value-size="1M"/><!-- Values large of 1Mbyte -->

        </filters>

    .........

    </value-storages>

Where value-storage is the subclass of org.exoplatform.services.jcr.storage.value.ValueStoragePlugin and properties are optional plugin specific parameters.

filters : Each file value storage can have the filter(s) for incoming values. If there are several filter criteria, they all have to match (AND-Condition).

A filter can match values by property type (property-type), property name (property-name), ancestor path (ancestor-path) and/or the size of values stored (min-value-size, e.g. 1M, 4.2G, 100 (bytes)).

In a code sample, we use a filter with property-type and min-value-size only. That means that the storage is only for binary values whose size is greater than 1Mbyte.

It's recommended to store properties with large values in a file value storage only.

## 1.21. REST Services on Groovy

Starting from version 1.9, JCR Service supports REST services creation on Groovy script.

The feature bases on RESTful framework and uses ResourceContainer concept.

### 1.21.1. Usage

Scripts should extend ResourceContainer and should be stored in JCR as a node of type exo:groovyResourceContainer.

Detailed REST services step-by-step implementation check there Create REST service step by step.

Component configuration enables Groovy services loader:

    <component>

      <type>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader</type>

      <init-params>

        <object-param>

          <name>observation.config</name>

          <object type="org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader$ObservationListenerConfiguration">

            <field name="repository">

              <string>repository</string>

            </field>

            <field name="workspaces">

              <collection type="java.util.ArrayList">

                <value>

                  <string>collaboration</string>

                </value>

              </collection>

            </field>

          </object>

        </object-param>

      </init-params>

    </component>

## 1.22. Configuring JBoss AS with eXo JCR in cluster

### 1.22.1. Launching Cluster

#### Deploying eXo JCR to JBoss As

To deploy eXo JCR to JBoss, do the following steps:

1. Download the latest version of eXo JCR .ear file distribution.

2. Copy <jcr.ear> into <%jboss_home%/server/default/deploy>

3. Put exo-configuration.xml to the root <%jboss_home%/exo-configuration.xml>

4. Configure JAAS by inserting XML fragment shown below into <%jboss_home%/server/default/conf/login-config.xml>

       <application-policy name="exo-domain">

          <authentication>

             <login-module code="org.exoplatform.services.security.j2ee.JbossLoginModule" flag="required"></login-module>

          </authentication>

       </application-policy>

5. Ensure that you use JBossTS Transaction Service and JBossCache Transaction Manager. Your exo-configuration.xml must contain such parts:

       <component>

          <key>org.jboss.cache.transaction.TransactionManagerLookup</key>

          <type>org.jboss.cache.GenericTransactionManagerLookup</type>^

       </component>

       <component>

          <key>org.exoplatform.services.transaction.TransactionService</key>

          <type>org.exoplatform.services.transaction.jbosscache.JBossTransactionsService</type>

          <init-params>

             <value-param>

                <name>timeout</name>

                <value>300</value>

             </value-param>

          </init-params>

       </component>

6. Start server:

   - bin/run.sh for Unix

   - bin/run.bat for Windows

7. Try accessing `http://localhostu:8080/browser` with root/exo as login/password if you have done everything right, you'll get access to repository browser.

#### Configuring JCR to use external configuration

- To manually configure repository, create a new configuration file (e.g., exo-jcr-configuration.xml). For details, see JCR Configuration. Your configuration must look like:

      <repository-service default-repository="repository1">

         <repositories>

            <repository name="repository1" system-workspace="ws1" default-workspace="ws1">

               <security-domain>exo-domain</security-domain>

               <access-control>optional</access-control>

               <authentication-policy>org.exoplatform.services.jcr.impl.core.access.JAASAuthenticator</authentication-policy>

               <workspaces>

                  <workspace name="ws1">

                     <container class="org.exoplatform.services.jcr.impl.storage.jdbc.optimisation.CQJDBCWorkspaceDataContainer">

                        <properties>

                           <property name="source-name" value="jdbcjcr" />

                           <property name="dialect" value="oracle" />

                           <property name="multi-db" value="false" />

                           <property name="update-storage" value="false" />

                           <property name="max-buffer-size" value="200k" />

                           <property name="swap-directory" value="../temp/swap/production" />

                        </properties>

                        <value-storages>

                           see "

      " part.

                        </value-storages>

                     </container>

                     <initializer class="org.exoplatform.services.jcr.impl.core.ScratchWorkspaceInitializer">

                        <properties>

                           <property name="root-nodetype" value="nt:unstructured" />

                        </properties>

                     </initializer>

                     <cache enabled="true" class="org.exoplatform.services.jcr.impl.dataflow.persistent.jbosscache.JBossCacheWorkspaceStorageCache">

                           see  "

      " part.

                     </cache>

                     <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

                        see  "

      " part.

                     </query-handler>

                     <lock-manager class="org.exoplatform.services.jcr.impl.core.lock.jbosscache.CacheableLockManagerImpl">

                        see  "

      " part.

                     </lock-manager>

                  </workspace>

                  <workspace name="ws2">

                              ...

                  </workspace>

                  <workspace name="wsN">

                              ...

                  </workspace>

               </workspaces>

            </repository>

         </repositories>

      </repository-service>

- Then, update RepositoryServiceConfiguration configuration in exo-configuration.xml to use this file:

      <component>

         <key>org.exoplatform.services.jcr.config.RepositoryServiceConfiguration</key>

         <type>org.exoplatform.services.jcr.impl.config.RepositoryServiceConfigurationImpl</type>

         <init-params>

            <value-param>

               <name>conf-path</name>

               <description>JCR configuration file</description>

               <value>exo-jcr-configuration.xml</value>

            </value-param>

         </init-params>

      </component>

### 1.22.2. Requirements

#### Environment requirements

- Every node of cluster MUST have the same mounted Network File System with the read and write permissions on it.

  "/mnt/tornado" - path to the mounted Network File System (all cluster nodes must use the same NFS).

- Every node of cluster MUST use the same database.

- The same Clusters on different nodes MUST have the same names (e.g., if Indexer cluster in workspace production on the first node has the name "production_indexer_cluster", then indexer clusters in workspace production on all other nodes MUST have the same name "production_indexer_cluster" ).

#### Configuration requirements

Configuration of every workspace in repository must contains of such parts:

- Value Storage configuration:

      <value-storages>

         <value-storage id="system" class="org.exoplatform.services.jcr.impl.storage.value.fs.TreeFileValueStorage">

            <properties>

               <property name="path" value="/mnt/tornado/temp/values/production" />    <!--path within NFS where ValueStorage will hold it's data-->

            </properties>

            <filters>

               <filter property-type="Binary" />

            </filters>

         </value-storage>

      </value-storages>

- Cache configuration:

      <cache enabled="true" class="org.exoplatform.services.jcr.impl.dataflow.persistent.jbosscache.JBossCacheWorkspaceStorageCache">

         <properties>

            <property name="jbosscache-configuration" value="jar:/conf/portal/test-jbosscache-data.xml" />     <!--    path to JBoss Cache configuration for data storage -->

            <property name="jgroups-configuration" value="jar:/conf/portal/udp-mux.xml" />                     <!--    path to JGroups configuration -->

            <property name="jbosscache-cluster-name" value="JCR_Cluster_cache" />                              <!--    JBoss Cache data storage cluster name -->

            <property name="jgroups-multiplexer-stack" value="false" />

            <property name="jbosscache-shareable" value="true" />

         </properties>

      </cache>

- Indexer configuration:

      <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

         <properties>

            <property name="changesfilter-class" value="org.exoplatform.services.jcr.impl.core.query.jbosscache.JBossCacheIndexChangesFilter" />

            <property name="index-dir" value="/mnt/tornado/temp/jcrlucenedb/production" />                       <!--    path within NFS where ValueStorage will hold it's data -->

            <property name="jbosscache-configuration" value="jar:/conf/portal/test-jbosscache-indexer.xml" />    <!--    path to JBoss Cache configuration for indexer -->

            <property name="jgroups-configuration" value="jar:/conf/portal/udp-mux.xml" />                       <!--    path to JGroups configuration -->

            <property name="jbosscache-cluster-name" value="JCR_Cluster_indexer" />                              <!--    JBoss Cache indexer cluster name -->

            <property name="jgroups-multiplexer-stack" value="false" />

            <property name="jbosscache-shareable" value="true" />

         </properties>

      </query-handler>

- Lock Manager configuration:

      <lock-manager class="org.exoplatform.services.jcr.impl.core.lock.jbosscache.CacheableLockManagerImpl">

         <properties>

            <property name="time-out" value="15m" />

            <property name="jbosscache-configuration" value="jar:/conf/portal/test-jbosscache-lock.xml" />       <!--    path to JBoss Cache configuration for lock manager -->

            <property name="jgroups-configuration" value="jar:/conf/portal/udp-mux.xml" />                       <!--    path to JGroups configuration -->

            <property name="jbosscache-cluster-name" value="JCR_Cluster_locks" />                                <!--    JBoss Cache locks cluster name -->

            <property name="jbosscache-cl-cache.jdbc.table.name" value="jcrlocks"/>                              <!--    the name of the DB table where lock's data will be stored -->

            <property name="jbosscache-cl-cache.jdbc.table.create" value="true"/>

            <property name="jbosscache-cl-cache.jdbc.table.drop" value="false"/>

            <property name="jbosscache-cl-cache.jdbc.table.primarykey" value="jcrlocks_pk"/>

            <property name="jbosscache-cl-cache.jdbc.fqn.column" value="fqn"/>

            <property name="jbosscache-cl-cache.jdbc.node.column" value="node"/>

            <property name="jbosscache-cl-cache.jdbc.parent.column" value="parent"/>

            <property name="jbosscache-cl-cache.jdbc.datasource" value="jdbcjcr"/>

            <property name="jgroups-multiplexer-stack" value="false" />

            <property name="jbosscache-shareable" value="true" />

         </properties>

      </lock-manager>

### 1.22.3. How to stop the node properly

To be sure that all transactions are over and JCR is in consistent state after stopping node, you need to follow next steps:

- Connect using JMX to one of cluster's node which you won't need to stop.

- Use RepositorySuspendController suspend all repositories (see 37.1 Repository suspending)

- Stop the node

- Use RepositorySuspendController to resume all repositories (see 37.3 Repository resuming)

## 1.23. JBoss Cache configuration

This section will show you how to use and configure Jboss Cache in the clustered environment. Also, you will know how to use a template-based configuration offered by eXo JCR for JBoss Cache instances.

### 1.23.1. JBoss cache configuration for indexer, lock manager and data container

Each mentioned components uses instances of JBoss Cache product for caching in clustered environment. So every element has its own transport and has to be configured in a proper way. As usual, workspaces have similar configuration but with different cluster-names and may-be some other parameters. The simplest way to configure them is to define their own configuration files for each component in each workspace:

    <property name="jbosscache-configuration" value="conf/standalone/test-jbosscache-lock-db1-ws1.xml" />

But if there are few workspaces, configuring them in such a way can be painful and hard-manageable. eXo JCR offers a template-based configuration for JBoss Cache instances. You can have one template for Lock Manager, one for Indexer and one for data container and use them in all the workspaces, defining the map of substitution parameters in a main configuration file. Just simply define ${jbosscache-<parameter name>} inside xml-template and list correct value in JCR configuration file just below "jbosscache-configuration", as shown:

Template:

    ...

    <clustering mode="replication" clusterName="${jbosscache-cluster-name}">

      <stateRetrieval timeout="20000" fetchInMemoryState="false" />

    ...

and JCR configuration file:

    ...

    <property name="jbosscache-configuration" value="jar:/conf/portal/jbosscache-lock.xml" />

    <property name="jbosscache-cluster-name" value="JCR-cluster-locks-db1-ws" />

    ...

### 1.23.2. JGroups configuration

JGroups is used by JBoss Cache for network communications and transport in a clustered environment. If property "jgroups-configuration" is defined in component configuration, it will be injected into the JBoss Cache instance on startup.

    <property name="jgroups-configuration" value="your/path/to/modified-udp.xml" />

As mentioned above, each component (lock manager, data container and query handler) for each workspace requires its own clustered environment. In other words, they have their own clusters with unique names. By default, each cluster should perform multi-casts on a separate port. This configuration leads to much unnecessary overhead on cluster. That's why JGroups offers multiplexer feature, providing ability to use one single channel for set of clusters. This feature reduces network overheads and increase performance and stability of application. To enable multiplexer stack, you should define appropriate configuration file (upd-mux.xml is pre-shipped one with eXo JCR) and set "jgroups-multiplexer-stack" to "true".

    <property name="jgroups-configuration" value="jar:/conf/portal/udp-mux.xml" />

    <property name="jgroups-multiplexer-stack" value="true" />

It is now highly recommended to use the shared transport instead of the multiplexer, to do so simply disable the multiplexer stack in the configuration of each component by setting the property jgroups-multiplexer-stack to false then you will need to ensure that the format of your jgroups configuration is not anymore a jgroups stack definitions but a normal configuration and finally you will need to set the property singleton_name of your JGroups configuration to a unique name (this name must not be the same from one portal container to another).

    <property name="jgroups-configuration" value="jar:/conf/portal/udp-mux.xml" />

    <property name="jgroups-multiplexer-stack" value="false" />

### 1.23.3. Allow to share JBoss Cache instances

A JBoss Cache instance is quite resource consuming and by default we will have 3 JBoss Cache instances (one instance for the indexer, one for the lock manager and one for the data container) for each workspace, so if you intend to have a lot of workspaces it could make sense to decide to share one JBoss Cache instance with several cache instances of the same type (i.e. indexer, lock manager or data container). This feature is disabled by default and can be enabled at component configuration level (i.e. indexer configuration, lock manager configuration and/or data container configuration) by setting the property "jbosscache-shareable" to true as below:

    <property name="jbosscache-shareable" value="true" />

Once enabled this feature will allow the JBoss Cache instance used by the component to be re-used by another components of the same type (i.e. indexer, lock manager or data container) with the exact same JBoss Cache configuration (except the eviction configuration that cans be different), which means that all the parameters of type ${jbosscache-<parameter name>} must be identical between the components of same type of different workspaces. In other words, if we use the same values for the parameters of type ${jbosscache-<parameter name>} in each workspace, we will have only 3 JBoss Cache instances (one instance for the indexer, one for the lock manager and one for the data container) used whatever the total amount of workspaces defined.

### 1.23.4. Shipped JBoss Cache configuration templates

eXo JCR implementation is shipped with ready-to-use JBoss Cache configuration templates for JCR's components. They are situated in application package in /conf/porta/ folder.

#### Data container template

Data container template is "jbosscache-data.xml":

    <?xml version="1.0" encoding="UTF-8"?>

    <jbosscache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:jboss:jbosscache-core:config:3.1">

       <locking useLockStriping="false" concurrencyLevel="500" lockParentForChildInsertRemove="false"

          lockAcquisitionTimeout="20000" />

       <clustering mode="replication" clusterName="${jbosscache-cluster-name}">

          <stateRetrieval timeout="20000" fetchInMemoryState="false" />

          <sync />

       </clustering>

       <!-- Eviction configuration -->

       <eviction wakeUpInterval="5000">

          <default algorithmClass="org.jboss.cache.eviction.ExpirationAlgorithm"

             actionPolicyClass="org.exoplatform.services.jcr.impl.dataflow.persistent.jbosscache.ParentNodeEvictionActionPolicy"

             eventQueueSize="1000000">

             <property name="maxNodes" value="1000000" />

             <property name="warnNoExpirationKey" value="false" />

          </default>

       </eviction>

    </jbosscache>

Variable
jbosscache-cluster-name

#### Lock manager template

It's template name is "jbosscache-lock.xml"

    <?xml version="1.0" encoding="UTF-8"?>

    <jbosscache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:jboss:jbosscache-core:config:3.1">

       <locking useLockStriping="false" concurrencyLevel="500" lockParentForChildInsertRemove="false"

          lockAcquisitionTimeout="20000" />

       <clustering mode="replication" clusterName="${jbosscache-cluster-name}">

          <stateRetrieval timeout="20000" fetchInMemoryState="false" />

          <sync />

       </clustering>

       <loaders passivation="false" shared="true">

          <preload>

             <node fqn="/" />

          </preload>

          <loader class="org.exoplatform.services.jcr.impl.core.lock.jbosscache.JDBCCacheLoader" async="false" fetchPersistentState="false"

             ignoreModifications="false" purgeOnStartup="false">

             <properties>

                cache.jdbc.table.name=${jbosscache-cl-cache.jdbc.table.name}

                cache.jdbc.table.create=${jbosscache-cl-cache.jdbc.table.create}

                cache.jdbc.table.drop=${jbosscache-cl-cache.jdbc.table.drop}

                cache.jdbc.table.primarykey=${jbosscache-cl-cache.jdbc.table.primarykey}

                cache.jdbc.fqn.column=${jbosscache-cl-cache.jdbc.fqn.column}

                cache.jdbc.fqn.type=${jbosscache-cl-cache.jdbc.fqn.type}

                cache.jdbc.node.column=${jbosscache-cl-cache.jdbc.node.column}

                cache.jdbc.node.type=${jbosscache-cl-cache.jdbc.node.type}

                cache.jdbc.parent.column=${jbosscache-cl-cache.jdbc.parent.column}

                cache.jdbc.datasource=${jbosscache-cl-cache.jdbc.datasource}

             </properties>

          </loader>

       </loaders>

    </jbosscache>

### Note

To prevent any consistency issue regarding the lock data please ensure that your cache loader is org.exoplatform.services.jcr.impl.core.lock.jbosscache.JDBCCacheLoader and that your database engine is transactional.

Variable
jbosscache-cluster-name
jbosscache-cl-cache.jdbc.table.name
jbosscache-cl-cache.jdbc.table.create
jbosscache-cl-cache.jdbc.table.drop
jbosscache-cl-cache.jdbc.table.primarykey
jbosscache-cl-cache.jdbc.fqn.column
jbosscache-cl-cache.jdbc.fqn.type
jbosscache-cl-cache.jdbc.node.column
jbosscache-cl-cache.jdbc.node.type
jbosscache-cl-cache.jdbc.parent.column
jbosscache-cl-cache.jdbc.datasource

#### Query handler (indexer) template

Have a look at "jbosscache-indexer.xml"

    <?xml version="1.0" encoding="UTF-8"?>

    <jbosscache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:jboss:jbosscache-core:config:3.1">

       <locking useLockStriping="false" concurrencyLevel="500" lockParentForChildInsertRemove="false"

          lockAcquisitionTimeout="20000" />

       <clustering mode="replication" clusterName="${jbosscache-cluster-name}">

          <stateRetrieval timeout="20000" fetchInMemoryState="false" />

          <sync />

       </clustering>

    </jbosscache>

Variable
jbosscache-cluster-name

## 1.24. LockManager configuration

What LockManager does?

In general, LockManager stores Lock objects, so it can give a Lock object or can release it.

Also, LockManager is responsible for removing Locks that live too long. This parameter may be configured with "time-out" property.

JCR provides two basic implementations of LockManager:

- `org.exoplatform.services.jcr.impl.core.lock.LockManagerImpl`;

- `org.exoplatform.services.jcr.impl.core.lock.jbosscache.CacheableLockManagerImpl`;

In this article, we will mostly mention about CacheableLockManagerImpl.

You can enable LockManager by adding lock-manager-configuration to workspace-configuration.

For example:

    <workspace name="ws">

       ...

       <lock-manager class="org.exoplatform.services.jcr.impl.core.lock.jbosscache.CacheableLockManagerImpl">

          <properties>

             <property name="time-out" value="15m" />

             ...

          </properties>

       </lock-manager>

       ...

    </workspace>

### 1.24.1. LockManagerImpl

LockManagerImpl is a simple implementation of LockManager, and also faster than CacheableLockManager. It stores Lock objects in HashMap and may also persist Locks if LockPersister is configured. LockManagerImpl does not support replication in any way.

See more about LockManager Configuration at here.

### 1.24.2. CacheableLockManagerImpl

CacheableLockManagerImpl stores Lock objects in JBoss-cache, so Locks are replicable and affect on cluster, not only a single node. Also, JBoss-cache has JDBCCacheLoader, so Locks will be stored to the database.

Both of the implementations support to remove Expired Locks. LockRemover separates threads, that periodically ask LockManager to remove Locks that live so long. So, the timeout for LockRemover may be set as follows, the default value is 30m.

    <properties>

       <property name="time-out" value="10m" />

       ...

    </properties>

#### Configuration

Replication requirements are the same for Cache.

You can see a full JCR configuration example at here.

Common tips:

- `clusterName` ("jbosscache-cluster-name") must be unique;

- `cache.jdbc.table.name` must be unique per datasource;

- `cache.jdbc.fqn.type` and cache.jdbc.node.type must be configured according to your database;

There are a few ways to configure CacheableLockManagerImpl, and all of them configure JBoss-cache and JDBCCacheLoader.

See http://community.jboss.org/wiki/JBossCacheJDBCCacheLoader

#### Simple JbossCache Configuration

The first one is putting JbossCache configuraion file path to CacheableLockManagerImpl.

### Note

This configuration is not so good as you think. Because the repository may contain many workspaces, and each workspace must contain LockManager configuration, and LockManager configuration may contain the JbossCache config file. So, the total configuration will grow up. But it is useful if we want to have a single LockManager with a special configuration.

Configuration is as follows:

    <lock-manager class="org.exoplatform.services.jcr.impl.core.lock.jbosscache.CacheableLockManagerImpl">

       <properties>

         <property name="time-out" value="15m" />

         <property name="jbosscache-configuration" value="conf/standalone/cluster/test-jbosscache-lock-config.xml" />

       </properties>

    </lock-manager>

`test-jbosscache-lock-config.xml`

    <?xml version="1.0" encoding="UTF-8"?>

    <jbosscache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:jboss:jbosscache-core:config:3.2">

     <locking useLockStriping="false" concurrencyLevel="500" lockParentForChildInsertRemove="false" lockAcquisitionTimeout="20000" />

     <clustering mode="replication" clusterName="JBoss-Cache-Lock-Cluster_Name">

      <stateRetrieval timeout="20000" fetchInMemoryState="false" />

      <sync />

     </clustering>

     <loaders passivation="false" shared="true">

      <preload>

       <node fqn="/" />

      </preload>

      <loader class="org.exoplatform.services.jcr.impl.core.lock.jbosscache.JDBCCacheLoader" async="false" fetchPersistentState="false" ignoreModifications="false" purgeOnStartup="false">

       <properties>

        cache.jdbc.table.name=jcrlocks_ws

        cache.jdbc.table.create=true

        cache.jdbc.table.drop=false

        cache.jdbc.table.primarykey=jcrlocks_ws_pk

        cache.jdbc.fqn.column=fqn

        cache.jdbc.fqn.type=VARCHAR(512)

        cache.jdbc.node.column=node

        cache.jdbc.node.type=<BLOB>

        cache.jdbc.parent.column=parent

        cache.jdbc.datasource=jdbcjcr

       </properties>

      </loader>

     </loaders>

    </jbosscache>

Configuration requirements:

- <clustering mode="replication" clusterName="JBoss-Cache-Lock-Cluster_Name"> - the cluster name must be unique;

- `cache.jdbc.table.name` must be unique per datasource;

- `cache.jdbc.node.type` and `cache.jdbc.fqn.type` must be configured according to your database.

  ### Note

  To prevent any consistency issue regarding the lock data please ensure that your cache loader is org.exoplatform.services.jcr.impl.core.lock.jbosscache.JDBCCacheLoader and that your database engine is transactional.

#### Template JBossCache Configuration

The second one is using the template JBoss-cache configuration for all LockManagers.

Lock template configuration

`test-jbosscache-lock.xml`

    <?xml version="1.0" encoding="UTF-8"?>

    <jbosscache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:jboss:jbosscache-core:config:3.1">

       <locking useLockStriping="false" concurrencyLevel="500" lockParentForChildInsertRemove="false"

          lockAcquisitionTimeout="20000" />

       <clustering mode="replication" clusterName="${jbosscache-cluster-name}">

          <stateRetrieval timeout="20000" fetchInMemoryState="false" />

          <sync />

       </clustering>

       <loaders passivation="false" shared="true">

          <!-- All the data of the JCR locks needs to be loaded at startup -->

          <preload>

             <node fqn="/" />

          </preload>

          <!--

          For another cache-loader class you should use another template with

          cache-loader specific parameters

          ->

          <loader class="org.exoplatform.services.jcr.impl.core.lock.jbosscache.JDBCCacheLoader" async="false" fetchPersistentState="false"

             ignoreModifications="false" purgeOnStartup="false">

             <properties>

                cache.jdbc.table.name=${jbosscache-cl-cache.jdbc.table.name}

                cache.jdbc.table.create=${jbosscache-cl-cache.jdbc.table.create}

                cache.jdbc.table.drop=${jbosscache-cl-cache.jdbc.table.drop}

                cache.jdbc.table.primarykey=${jbosscache-cl-cache.jdbc.table.primarykey}

                cache.jdbc.fqn.column=${jbosscache-cl-cache.jdbc.fqn.column}

                cache.jdbc.fqn.type=${jbosscache-cl-cache.jdbc.fqn.type}

                cache.jdbc.node.column=${jbosscache-cl-cache.jdbc.node.column}

                cache.jdbc.node.type=${jbosscache-cl-cache.jdbc.node.type}

                cache.jdbc.parent.column=${jbosscache-cl-cache.jdbc.parent.column}

                cache.jdbc.datasource=${jbosscache-cl-cache.jdbc.datasource}

             </properties>

          </loader>

       </loaders>

    </jbosscache>

As you see, all configurable parameters are filled by templates and will be replaced by LockManagers configuration parameters:

    <lock-manager class="org.exoplatform.services.jcr.impl.core.lock.jbosscache.CacheableLockManagerImpl">

       <properties>

          <property name="time-out" value="15m" />

          <property name="jbosscache-configuration" value="test-jbosscache-lock.xml" />

          <property name="jgroups-configuration" value="udp-mux.xml" />

          <property name="jgroups-multiplexer-stack" value="false" />

          <property name="jbosscache-cluster-name" value="JCR-cluster-locks" />

          <property name="jbosscache-cl-cache.jdbc.table.name" value="jcrlocks" />

          <property name="jbosscache-cl-cache.jdbc.table.create" value="true" />

          <property name="jbosscache-cl-cache.jdbc.table.drop" value="false" />

          <property name="jbosscache-cl-cache.jdbc.table.primarykey" value="jcrlocks_pk" />

          <property name="jbosscache-cl-cache.jdbc.fqn.column" value="fqn" />

          <property name="jbosscache-cl-cache.jdbc.fqn.type" value="AUTO"/>

          <property name="jbosscache-cl-cache.jdbc.node.column" value="node" />

          <property name="jbosscache-cl-cache.jdbc.node.type" value="AUTO"/>

          <property name="jbosscache-cl-cache.jdbc.parent.column" value="parent" />

          <property name="jbosscache-cl-cache.jdbc.datasource" value="jdbcjcr" />

          <property name="jbosscache-shareable" value="true" />

       </properties>

    </lock-manager>

Configuration requirements:

- `jbosscache-cl-cache.jdbc.fqn.column` and `jbosscache-cl-cache.jdbc.node.type` is the same as cache.jdbc.fqn.type and cache.jdbc.node.type in JBoss-Cache configuration. You can set those data types according to your database type or set it as AUTO (or do not set at all) and data type will be detected automatically.

- As you see, jgroups-configuration is moved to separate the configuration file - udp-mux.xml. In this case, the udp-mux.xml file is a common JGroup configuration for all components (QueryHandler, Cache, LockManager), but we can still create our own configuration.

`our udp-mux.xml`

    <config>

        <UDP

             singleton_name="JCR-cluster"

             mcast_addr="${jgroups.udp.mcast_addr:228.10.10.10}"

             mcast_port="${jgroups.udp.mcast_port:45588}"

             tos="8"

             ucast_recv_buf_size="20000000"

             ucast_send_buf_size="640000"

             mcast_recv_buf_size="25000000"

             mcast_send_buf_size="640000"

             loopback="false"

             discard_incompatible_packets="true"

             max_bundle_size="64000"

             max_bundle_timeout="30"

             use_incoming_packet_handler="true"

             ip_ttl="${jgroups.udp.ip_ttl:2}"

             enable_bundling="false"

             enable_diagnostics="true"

             thread_naming_pattern="cl"

             use_concurrent_stack="true"

             thread_pool.enabled="true"

             thread_pool.min_threads="2"

             thread_pool.max_threads="8"

             thread_pool.keep_alive_time="5000"

             thread_pool.queue_enabled="true"

             thread_pool.queue_max_size="1000"

             thread_pool.rejection_policy="discard"

             oob_thread_pool.enabled="true"

             oob_thread_pool.min_threads="1"

             oob_thread_pool.max_threads="8"

             oob_thread_pool.keep_alive_time="5000"

             oob_thread_pool.queue_enabled="false"

             oob_thread_pool.queue_max_size="100"

             oob_thread_pool.rejection_policy="Run" />

        <PING timeout="2000"

                num_initial_members="3"/>

        <MERGE2 max_interval="30000"

                min_interval="10000"/>

       <FD_SOCK />

       <FD timeout="10000" max_tries="5" shun="true" />

       <VERIFY_SUSPECT timeout="1500" />

       <BARRIER />

        <pbcast.NAKACK use_stats_for_retransmission="false"

                       exponential_backoff="150"

                       use_mcast_xmit="true" gc_lag="0"

                       retransmit_timeout="50,300,600,1200"

                       discard_delivered_msgs="true"/>

       <UNICAST timeout="300,600,1200" />

        <pbcast.STABLE stability_delay="1000" desired_avg_gossip="50000"

                       max_bytes="1000000"/>

       <VIEW_SYNC avg_send_interval="60000" />

        <pbcast.GMS print_local_addr="true" join_timeout="3000"

                    shun="false"

                    view_bundling="true"/>

        <FC max_credits="500000"

                        min_threshold="0.20"/>

       <FRAG2 frag_size="60000" />

       <!--pbcast.STREAMING_STATE_TRANSFER /-->

       <pbcast.STATE_TRANSFER />

       <pbcast.FLUSH />

    </config>

#### Data Types in Different Databases

DataBase name | Node data type | FQN data type
--- | --- | ---
default | BLOB | VARCHAR(512)
HSSQL | OBJECT | VARCHAR(512)
MySQL | LONGBLOB | VARCHAR(512)
ORACLE | BLOB | VARCHAR2(512)
PostgreSQL | bytea | VARCHAR(512)
MSSQL | VARBINARY(MAX) | VARCHAR(512)
DB2 | BLOB | VARCHAR(512)
Sybase | IMAGE | VARCHAR(512)
Ingres | long byte | VARCHAR(512)

#### Lock migration from 1.12.x

There are 3 choices:

I. When new Shareable Cache feature is not going to be used and all locks should be kept after migration.

- Ensure that the same lock tables used in configuration;

- Start the server;

II. When new Shareable Cache feature is not going to be used and all locks should be removed after migration.

- Ensure that the same lock tables used in configuration;

- Start the sever WITH system property -Dorg.exoplatform.jcr.locks.force.remove=true;

- Stop the server;

- Start the server (WITHOUT system property -Dorg.exoplatform.jcr.locks.force.remove);

III. When new Shareable Cache feature will be used (in this case all locks are removed after migration).

- Start the sever WITH system property -Dorg.exoplatform.jcr.locks.force.remove=true;

- Stop the server;

- Start the server (WITHOUT system property -Dorg.exoplatform.jcr.locks.force.remove);

- (Not mandatory) manually remove old tables for lock;

## 1.25. QueryHandler configuration

This section shows you how to configure QueryHandler: Indexing in clustered environment.

### 1.25.1. Indexing in clustered environment

JCR offers multiple indexing strategies. They include both for standalone and clustered environments using the advantages of running in a single JVM or doing the best to use all resources available in cluster. JCR uses Lucene library as underlying search and indexing engine, but it has several limitations that greatly reduce possibilities and limits the usage of cluster advantages. That's why eXo JCR offers three strategies that are suitable for it's own usecases. They are standalone, clustered with shared index and clustered with local indexes. Each one has it's pros and cons.

Stanadlone strategy provides a stack of indexes to achieve greater performance within single JVM.

It combines in-memory buffer index directory with delayed file-system flushing. This index is called "Volatile" and it is invoked in searches also. Within some conditions volatile index is flushed to the persistent storage (file system) as new index directory. This allows to achieve great results for write operations.

Clustered implementation with local indexes is built upon same strategy with volatile in-memory index buffer along with delayed flushing on persistent storage.

As this implementation designed for clustered environment it has additional mechanisms for data delivery within cluster. Actual text extraction jobs done on the same node that does content operations (i.e. write operation). Prepared "documents" (Lucene term that means block of data ready for indexing) are replicated withing cluster nodes and processed by local indexes. So each cluster instance has the same index content. When new node joins the cluster it has no initial index, so it must be created. There are some supported ways of doing this operation. The simplest is to simply copy the index manually but this is not intended for use. If no initial index found JCR uses automated sceneries. They are controlled via configuration (see "index-recovery-mode" parameter) offering full re-indexing from database or copying from another cluster node.

For some reasons having a multiple index copies on each instance can be costly. So shared index can be used instead (see diagram below).

This indexing strategy combines advantages of in-memory index along with shared persistent index offering "near" real time search capabilities. This means that newly added content is accessible via search practically immediately. This strategy allows nodes to index data in their own volatile (in-memory) indexes, but persistent indexes are managed by single "coordinator" node only. Each cluster instance has a read access for shared index to perform queries combining search results found in own in-memory index also. Take in account that shared folder must be configured in your system environment (i.e. mounted NFS folder). But this strategy in some extremely rare cases can have a bit different volatile indexes within cluster instances for a while. In a few seconds they will be up2date.

See more about Search Configuration.

### 1.25.2. Configuration

#### Query-handler configuration overview

Configuration example:

    <workspace name="ws">

       <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

          <properties>

             <property name="index-dir" value="shareddir/index/db1/ws" />

             <property name="changesfilter-class"

                value="org.exoplatform.services.jcr.impl.core.query.jbosscache.JBossCacheIndexChangesFilter" />

             <property name="jbosscache-configuration" value="jbosscache-indexer.xml" />

             <property name="jgroups-configuration" value="udp-mux.xml" />

             <property name="jgroups-multiplexer-stack" value="true" />

             <property name="jbosscache-cluster-name" value="JCR-cluster-indexer-ws" />

             <property name="max-volatile-time" value="60" />

             <property name="rdbms-reindexing" value="true" />

             <property name="reindexing-page-size" value="1000" />

             <property name="index-recovery-mode" value="from-coordinator" />

             <property name="index-recovery-filter" value="org.exoplatform.services.jcr.impl.core.query.lucene.DocNumberRecoveryFilter" />

             <property name="indexing-thread-pool-size" value="16" />

          </properties>

       </query-handler>

    </workspace>

Property name | Description
--- | ---
index-dir | path to index
changesfilter-class | template of JBoss-cache configuration for all query-handlers in repository
jbosscache-configuration | template of JBoss-cache configuration for all query-handlers in repository
jgroups-configuration | jgroups-configuration is template configuration for all components (search, cache, locks) [Add link to document describing template configurations]
jgroups-multiplexer-stack | [TODO about jgroups-multiplexer-stack - add link to JBoss doc]
jbosscache-cluster-name | cluster name (must be unique)
max-volatile-time | max time to live for Volatile Index
rdbms-reindexing | indicate that need to use rdbms reindexing mechanism if possible, the default value is true
reindexing-page-size | maximum amount of nodes which can be retrieved from storage for re-indexing purpose, the default value is 100
index-recovery-mode | If the parameter has been set to from-indexing, so a full indexing will be automatically launched, if the parameter has been set to from-coordinator (default behavior), the index will be retrieved from coordinator
index-recovery-filter | Defines implementation class or classes of RecoveryFilters, the mechanism of index synchronization for Local Index strategy.
async-reindexing | Controls the process of re-indexing on JCR's startup. If flag set, indexing will be launched asynchronously, without blocking the JCR. Default is "false".
indexing-thread-pool-size | Define the total amount of indexing threads.

### Note

If you use postgreSQL and the parameter rdbms-reindexing is set to true, the performances of the queries used while indexing can be improved by setting the parameter "enable_seqscan" to "off" or "default_statistics_target" to at least "50" in the configuration of your database. Then you need to restart DB server and make analyze of the JCR_SVALUE (or JCR_MVALUE) table.

### Note

If you use DB2 and the parameter rdbms-reindexing is set to true, the performance of the queiries used while indexing can be improved by making statisticks on tables by running "RUNSTATS ON TABLE <scheme>.<table> WITH DISTRIBUTION AND INDEXES ALL" for JCR_SITEM (or JCR_MITEM) and JCR_SVALUE (or JCR_MVALUE) tables.

#### Standalone strategy

When running JCR in standalone usually standalone indexing is used also. Such parameters as "changesfilter-class", "jgroups-configuration" and all the "jbosscache-*" must be skipped and not defined. Like the configuration below.

    <workspace name="ws">

       <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

          <properties>

             <property name="index-dir" value="shareddir/index/db1/ws" />

             <property name="max-volatile-time" value="60" />

             <property name="rdbms-reindexing" value="true" />

             <property name="reindexing-page-size" value="1000" />

             <property name="index-recovery-mode" value="from-coordinator" />

          </properties>

       </query-handler>

    </workspace>

#### Cluster-ready indexing strategies

For both cluster-ready implementations JBoss Cache, JGroups and Changes Filter values must be defined. Shared index requires some kind of remote or shared file system to be attached in a system (i.e. NFS, SMB or etc). Indexing directory ("indexDir" value) must point to it. Setting "changesfilter-class" to "org.exoplatform.services.jcr.impl.core.query.jbosscache.JBossCacheIndexChangesFilter" will enable shared index implementation.

    <workspace name="ws">

       <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

          <properties>

             <property name="index-dir" value="/mnt/nfs_drive/index/db1/ws" />

             <property name="changesfilter-class"

                value="org.exoplatform.services.jcr.impl.core.query.jbosscache.JBossCacheIndexChangesFilter" />

             <property name="jbosscache-configuration" value="jbosscache-indexer.xml" />

             <property name="jgroups-configuration" value="udp-mux.xml" />

             <property name="jgroups-multiplexer-stack" value="false" />

             <property name="jbosscache-cluster-name" value="JCR-cluster-indexer" />

             <property name="max-volatile-time" value="60" />

             <property name="rdbms-reindexing" value="true" />

             <property name="reindexing-page-size" value="1000" />

             <property name="index-recovery-mode" value="from-coordinator" />

             <property name="jbosscache-shareable" value="true" />

          </properties>

       </query-handler>

    </workspace>

In order to use cluster-ready strategy based on local indexes, when each node has own copy of index on local file system, the following configuration must be applied. Indexing directory must point to any folder on local file system and "changesfilter-class" must be set to "org.exoplatform.services.jcr.impl.core.query.jbosscache.LocalIndexChangesFilter".

    <workspace name="ws">

       <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

          <properties>

             <property name="index-dir" value="/mnt/nfs_drive/index/db1/ws" />

             <property name="changesfilter-class"

                value="org.exoplatform.services.jcr.impl.core.query.jbosscache.LocalIndexChangesFilter" />

             <property name="jbosscache-configuration" value="jbosscache-indexer.xml" />

             <property name="jgroups-configuration" value="udp-mux.xml" />

             <property name="jgroups-multiplexer-stack" value="false" />

             <property name="jbosscache-cluster-name" value="JCR-cluster-indexer" />

             <property name="max-volatile-time" value="60" />

             <property name="rdbms-reindexing" value="true" />

             <property name="reindexing-page-size" value="1000" />

             <property name="index-recovery-mode" value="from-coordinator" />

             <property name="jbosscache-shareable" value="true" />

          </properties>

       </query-handler>

    </workspace>

##### Local Index Recovery Filters

Common usecase for all cluster-ready applications is a hot joining and leaving of processing units. Node that is joining cluster for the first time or node joining after some downtime, they all must be in a synchronized state. When having a deal with shared value storages, databases and indexes, cluster nodes are synchronized anytime. But it's an issue when local index strategy used. If new node joins cluster, having no index it is retrieved or recreated. Node can be restarted also and thus index not empty. Usually existing index is thought to be actual, but can be outdated. JCR offers a mechanism called RecoveryFilters that will automatically retrieve index for the joining node on startup. This feature is a set of filters that can be defined via QueryHandler configuration:

    <property name="index-recovery-filter" value="org.exoplatform.services.jcr.impl.core.query.lucene.DocNumberRecoveryFilter" />

Filter number is not limited so they can be combined:

    <property name="index-recovery-filter" value="org.exoplatform.services.jcr.impl.core.query.lucene.DocNumberRecoveryFilter" />

    <property name="index-recovery-filter" value="org.exoplatform.services.jcr.impl.core.query.lucene.SystemPropertyRecoveryFilter" />

If any one fires, the index is re-synchronized. Please take in account, that DocNumberRecoveryFilter is used in cases when no filter configured. So, if resynchronization should be blocked, or strictly required on start, then ConfigurationPropertyRecoveryFilter can be used.

This feature uses standard index recovery mode defined by previously described parameter (can be "from-indexing" or "from-coordinator" (default value))

    <property name="index-recovery-mode" value="from-coordinator" />

There are couple implementations of filters:

- org.exoplatform.services.jcr.impl.core.query.lucene.DummyRecoveryFilter: always returns true, for cases when index must be force resynchronized (recovered) each time;

- org.exoplatform.services.jcr.impl.core.query.lucene.SystemPropertyRecoveryFilter : return value of system property "org.exoplatform.jcr.recoveryfilter.forcereindexing". So index recovery can be controlled from the top without changing documentation using system properties;

- org.exoplatform.services.jcr.impl.core.query.lucene.ConfigurationPropertyRecoveryFilter : return value of QueryHandler configuration property "index-recovery-filter-forcereindexing". So index recovery can be controlled from configuration separately for each workspace. I.e:

      <property name="index-recovery-filter" value="org.exoplatform.services.jcr.impl.core.query.lucene.ConfigurationPropertyRecoveryFilter" />

      <property name="index-recovery-filter-forcereindexing" value="true" />

- org.exoplatform.services.jcr.impl.core.query.lucene.DocNumberRecoveryFilter : checks number of documents in index on coordinator side and self-side. Return true if differs. Advantage of this filter comparing to other, it will skip reindexing for workspaces where index wasn't modified. I.e. there is 10 repositories with 3 workspaces in each one. Only one is really heavily used in cluster : frontend/production. So using this filter will only reindex those workspaces that are really changed, without affecting other indexes thus greatly reducing startup time.

#### JBoss-Cache template configuration

JBoss-Cache template configuration for query handler is about the same for both clustered strategies.

jbosscache-indexer.xml

    <?xml version="1.0" encoding="UTF-8"?>

    <jbosscache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:jboss:jbosscache-core:config:3.1">

       <locking useLockStriping="false" concurrencyLevel="50000" lockParentForChildInsertRemove="false"

          lockAcquisitionTimeout="20000" />

       <!-- Configure the TransactionManager -->

       <transaction transactionManagerLookupClass="org.jboss.cache.transaction.JBossStandaloneJTAManagerLookup" />

       <clustering mode="replication" clusterName="${jbosscache-cluster-name}">

          <stateRetrieval timeout="20000" fetchInMemoryState="false" />

          <sync />

       </clustering>

    </jbosscache>

See more about template configurations here.

### 1.25.3. Asynchronous reindexing

Managing a big set of data using JCR in production environment sometimes requires special operations with Indexes, stored on File System. One of those maintenance operations is a recreation of it. Also called "re-indexing". There are various usecases when it's important to do. They include hardware faults, hard restarts, data-corruption, migrations and JCR updates that brings new features related to index. Usually index re-creation requested on server's startup or in runtime.

#### On startup indexing

Common usecase for updating and re-creating the index is to stop the server and manually remove indexes for workspaces requiring it. When server will be started, missing indexes are automatically recovered by re-indexing. JCR Supports direct RDBMS re-indexing, that usually is faster than ordinary and can be configured via QueryHandler parameter "rdbms-reindexing" set to "true" (for more information please refer to "Query-handler configuration overview"). New feature to introduce is asynchronous indexing on startup. Usually startup is blocked until process is finished. Block can take any period of time, depending on amount of data persisted in repositories. But this can be resolved by using an asynchronous approaches of startup indexation. Saying briefly, it performs all operations with index in background, without blocking the repository. This is controlled by the value of "async-reindexing" parameter in QueryHandler configuration. With asynchronous indexation active, JCR starts with no active indexes present. Queries on JCR still can be executed without exceptions, but no results will be returned until index creation completed. Checking index state is possible via QueryManagerImpl:

    boolean online = ((QueryManagerImpl)Workspace.getQueryManager()).getQueryHandeler().isOnline();

"OFFLINE" state means that index is currently re-creating. When state changed, corresponding log event is printed. From the start of background task index is switched to "OFFLINE", with following log event :

    [INFO] Setting index OFFLINE (repository/production[system]).

When process finished, two events are logged :

    [INFO] Created initial index for 143018 nodes (repository/production[system]).
    [INFO] Setting index ONLINE (repository/production[system]).

Those two log lines indicates the end of process for workspace given in brackets. Calling isOnline() as mentioned above, will also return true.

#### Hot Asynchronous Workspace Reindexing via JMX

Some hard system faults, error during upgrades, migration issues and some other factors may corrupt the index. Most likely end customers would like the production systems to fix index issues in run-time, without delays and restarts. Current versions of JCR supports "Hot Asynchronous Workspace Reindexing" feature. It allows end-user (Service Administrator) to launch the process in background without stopping or blocking whole application by using any JMX-compatible console (see screenshot below, "JConsole in action").

Server can continue working as expected while index is recreated. This depends on the flag "allow queries", passed via JMX interface to reindex operation invocation. If the flag set, then application continues working. But there is one critical limitation the end-users must be aware. Index is frozen while background task is running. It meant that queries are performed on index present on the moment of task startup and data written into repository after startup won't be available through the search until process finished. Data added during re-indexation is also indexed, but will be available only when task is done. Briefly, JCR makes the snapshot of indexes on asynch task startup and uses it for searches. When operation finished, stale indexes replaced by newly created including newly added data. If flag "allow queries" is set to false, then all queries will throw an exception while task is running. Current state can be acquired using the following JMX operation:

- getHotReindexingState() - returns information about latest invocation: start time, if in progress or finish time if done.

#### Notices

First of all, can't launch Hot re-indexing via JMX if index is already in offline mode. It means that index is currently is invoked in some operations, like re-indexing at startup, copying in cluster to another node or whatever. Another important this is Hot Asynchronous Reindexing via JMX and "on startup" reindexing are completely different features. So you can't get the state of startup reindexing using command getHotReindexingState in JMX interface, but there are some common JMX operations:

- getIOMode - returns current index IO mode (READ_ONLY / READ_WRITE), belongs to clustered configuration states;

- getState - returns current state: ONLINE / OFFLINE.

### 1.25.4. Advanced tuning

#### Lucene tuning

As mentioned above, JCR Indexing is based on Lucene indexing library as underlying search engine. It uses Directories to store index and manages access to index by Lock Factories. By default JCR implementation uses optimal combination of Directory implementation and Lock Factory implementation. When running on OS different from Windows, NIOFSDirectory implementation used. And SimpleFSDirectory for Windows stations. NativeFSLockFactory is an optimal solution for wide variety of cases including clustered environment with NFS shared resources. But those default can be overridden with the help of system properties. There are two properties: "org.exoplatform.jcr.lucene.store.FSDirectoryLockFactoryClass" and "org.exoplatform.jcr.lucene.FSDirectory.class" that are responsible for changing default behavior. First one defines implementation of abstract Lucene LockFactory class and the second one sets implementation class for FSDirectory instances. For more information please refer to Lucene documentation. But be sure You know what You are changing. JCR allows end users to change implementation classes of Lucene internals, but doesn't guarantee it's stability and functionality.

## 1.26. JBossTransactionsService

JBossTransactionsService implements eXo TransactionService and provides access to JBoss Transaction Service (JBossTS) JTA implementation via eXo container dependency.

TransactionService is used in JCR cache org.exoplatform.services.jcr.impl.dataflow.persistent.jbosscache.JBossCacheWorkspaceStorageCache implementaion. See Cluster configuration for example.

### 1.26.1. Configuration

Example configuration:

      <component>

        <key>org.exoplatform.services.transaction.TransactionService</key>

        <type>org.exoplatform.services.transaction.jbosscache.JBossTransactionsService</type>

        <init-params>

          <value-param>

            <name>timeout</name>

            <value>3000</value>

          </value-param>

        </init-params>

      </component>

timeout - XA transaction timeout in seconds

## 1.27. TransactionManagerLookup

JBossCache class is registered as an eXo container component in the configuration.xml file.

      <component>

         <key>org.jboss.cache.transaction.TransactionManagerLookup</key>

         <type>org.jboss.cache.transaction.JBossStandaloneJTAManagerLookup</type>

      </component>

JBossStandaloneJTAManagerLookup is used in a standalone environment, but GenericTransactionManagerLookup is used in the Application Server environment.

## 1.28. RepositoryCreationService

RepositoryCreationService is the service which is used to create repositories in runtime. The service can be used in a standalone or cluster environment.

### 1.28.1. Dependencies

RepositoryConfigurationService depends to next components:

- DBCreator - DBCreator used to create new database for each unbinded datasource.

- BackupManager - BackupManager used to created repository from backup.

- RPCService - RPCService used for communication between cluster-nodes

  ### Note

  RPCService may not be configured - in this case, RepositoryService will work as standalone service.

### 1.28.2. How it works

- User executes reserveRepositoryName(String repositoryName) - client-node calls coordinator-node to reserve repositoryName. If this name is already reserved or repository with this name exist, client-node will fetch RepositoryCreationException. If not Client will get token string.

- than user executes createRepository(String backupId, RepositoryEntry rEntry, String token). Coordinator-node checks the token, and creates Repository.

- whan repository become created - user-node broadcast message to all clusterNodes with RepositoryEntry, so each cluster node starts new Repository.

There is two ways to create repositry: make it in single step - just call createRepository(String backupId, RepositoryEntry); or reserve repositoryName at first (reserveRepositoryName(String repositoryName)), than create reserved repository (createRepository(String backupId, RepositoryEntry rEntry, String token)).

### 1.28.3. Configuration

RepositoryCreationService configuration

    <component>

       <key>org.exoplatform.services.jcr.ext.backup.BackupManager</key>

       <type>org.exoplatform.services.jcr.ext.backup.impl.BackupManagerImpl</type>

       <init-params>

          <properties-param>

             <name>backup-properties</name>

             <property name="backup-dir" value="target/backup" />

          </properties-param>

       </init-params>

    </component>

    <component>

       <key>org.exoplatform.services.database.creator.DBCreator</key>

       <type>org.exoplatform.services.database.creator.DBCreator</type>

       <init-params>

          <properties-param>

             <name>db-connection</name>

             <description>database connection properties</description>

             <property name="driverClassName" value="org.hsqldb.jdbcDriver" />

             <property name="url" value="jdbc:hsqldb:file:target/temp/data/" />

             <property name="username" value="sa" />

             <property name="password" value="" />

          </properties-param>

          <properties-param>

             <name>db-creation</name>

             <description>database creation properties</description>

             <property name="scriptPath" value="src/test/resources/test.sql" />

             <property name="username" value="sa" />

             <property name="password" value="" />

          </properties-param>

       </init-params>

    </component>

    <component>

       <key>org.exoplatform.services.rpc.RPCService</key>

       <type>org.exoplatform.services.rpc.impl.RPCServiceImpl</type>

       <init-params>

          <value-param>

             <name>jgroups-configuration</name>

             <value>jar:/conf/standalone/udp-mux.xml</value>

          </value-param>

          <value-param>

             <name>jgroups-cluster-name</name>

             <value>RPCService-Cluster</value>

          </value-param>

          <value-param>

             <name>jgroups-default-timeout</name>

             <value>0</value>

          </value-param>

       </init-params>

    </component>

    <component>

       <key>org.exoplatform.services.jcr.ext.repository.creation.RepositoryCreationService</key>

       <type>

          org.exoplatform.services.jcr.ext.repository.creation.RepositoryCreationServiceImpl

       </type>

         <init-params>

             <value-param>

                <name>factory-class-name</name>

                <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>

             </value-param>

          </init-params>

    </component>

- factory-class-name - is not mandatory parameter, indicates what the factory need to use to create DataSource objects

### 1.28.4. RepositoryCreationService Interface

    public interface RepositoryCreationService

    {

       /**

        * Reserves, validates and creates repository in a simplified form.

        *

        * @param rEntry - repository Entry - note that datasource must not exist.

        * @param backupId - backup id

        * @param creationProps - storage creation properties

        * @throws RepositoryConfigurationException

        *          if some exception occurred during repository creation or repository name is absent in reserved list

        * @throws RepositoryCreationServiceException

        *          if some exception occurred during repository creation or repository name is absent in reserved list

        */

       void createRepository(String backupId, RepositoryEntry rEntry, StorageCreationProperties creationProps)

          throws RepositoryConfigurationException, RepositoryCreationException;n

       /**

        * Reserves, validates and creates repository in a simplified form.

        *

        * @param rEntry - repository Entry - note that datasource must not exist.

        * @param backupId - backup id

        * @throws RepositoryConfigurationException

        *          if some exception occurred during repository creation or repository name is absent in reserved list

        * @throws RepositoryCreationServiceException

        *          if some exception occurred during repository creation or repository name is absent in reserved list

        */

       void createRepository(String backupId, RepositoryEntry rEntry) throws RepositoryConfigurationException,

          RepositoryCreationException;

       /**

        * Reserve repository name to prevent repository creation with same name from other place in same time

        * via this service.

        *

        * @param repositoryName - repositoryName

        * @return repository token. Anyone obtaining a token can later create a repository of reserved name.

        * @throws RepositoryCreationServiceException if can't reserve name

        */

       String reserveRepositoryName(String repositoryName) throws RepositoryCreationException;

       /**

        * Creates repository, using token of already reserved repository name.

        * Good for cases, when repository creation should be delayed or made asynchronously in dedicated thread.

        *

        * @param rEntry - repository entry - note, that datasource must not exist

        * @param backupId - backup id

        * @param rToken - token

        * @param creationProps - storage creation properties

        * @throws RepositoryConfigurationException

        *          if some exception occurred during repository creation or repository name is absent in reserved list

        * @throws RepositoryCreationServiceException

        *          if some exception occurred during repository creation or repository name is absent in reserved list

        */

       void createRepository(String backupId, RepositoryEntry rEntry, String rToken, StorageCreationProperties creationProps)

          throws RepositoryConfigurationException, RepositoryCreationException;

       /**

        * Creates  repository, using token of already reserved repository name. Good for cases, when repository creation should be delayed or

        * made asynchronously in dedicated thread.

        *

        * @param rEntry - repository entry - note, that datasource must not exist

        * @param backupId - backup id

        * @param rToken - token

        * @throws RepositoryConfigurationException

        *          if some exception occurred during repository creation or repository name is absent in reserved list

        * @throws RepositoryCreationServiceException

        *          if some exception occurred during repository creation or repository name is absent in reserved list

        */

       void createRepository(String backupId, RepositoryEntry rEntry, String rToken)

          throws RepositoryConfigurationException, RepositoryCreationException;

       /**

        * Remove previously created repository.

        *

        * @param repositoryName - the repository name to delete

        * @param forceRemove - force close all opened sessions

        * @throws RepositoryCreationServiceException

        *          if some exception occurred during repository removing occurred

        */

       void removeRepository(String repositoryName, boolean forceRemove) throws RepositoryCreationException;

    }

### 1.28.5. Conclusions and restrictions

- Each datasource in RepositoryEntry of new Repository must have unbinded datasources. Thats mean, such datasource must have not databases behind them. This restriction exists to avoid corruption of existing repositories data.

- RPCService is optional component, but without it, RepositoryCreatorService can not communicate with other cluster-nodes and works as standalone.

## 1.29. JCR Query Usecases

JCR supports two query languages - JCR and XPath. A query, whether XPath or SQL, specifies a subset of nodes within a workspace, called the result set. The result set constitutes all the nodes in the workspace that meet the constraints stated in the query.

### 1.29.1. Query Lifecycle

#### Query Creation and Execution

SQL

    // get QueryManager

    QueryManager queryManager = workspace.getQueryManager();�

    // make SQL query

    Query query = queryManager.createQuery("SELECT * FROM nt:base ", Query.SQL);

    // execute query

    QueryResult result = query.execute();

XPath

    // get QueryManager

    QueryManager queryManager = workspace.getQueryManager();

    // make XPath query

    Query query = queryManager.createQuery("//element(*,nt:base)", Query.XPATH);

    // execute query

    QueryResult result = query.execute();

#### Query Result Processing

    // fetch query result

    QueryResult result = query.execute();

Now we can get result in an iterator of nodes:

    NodeIterator it = result.getNodes();

or we get the result in a table:

    // get column names

    String[] columnNames = result.getColumnNames();

    // get column rows

    RowIterator rowIterator = result.getRows();

    while(rowIterator.hasNext()){

       // get next row

       Row row = rowIterator.nextRow();

       // get all values of row

       Value[] values = row.getValues();

    }

#### Scoring

The result returns a score for each row in the result set. The score contains a value that indicates a rating of how well the result node matches the query. A high value means a better matching than a low value. This score can be used for ordering the result.

eXo JCR Scoring is a mapping of Lucene scoring. For a more in-depth understanding, please study Lucene documentation.

jcr:score counted in next way - (lucene score)*1000f.

Score may be increased for specified nodes, see Index Boost Value

Also, see an example Order by Score

### 1.29.2. Query result settings

- Set Offset And Limit

### 1.29.3. Type Constraints

- Find All Nodes

- Find Nodes by Primary Type

- Find Nodes by Mixin Type

### 1.29.4. Property Constraints

- Property Comparison

- LIKE Constraint

- Escaping in LIKE Statements

- NOT Constraint

- AND Constraint

- OR Constraint

- Property Existence Constraint

- Upper and Lower Case Constraints

- Date Property Comparison

- Node Name Constraint

- Multivalue Property Comparison

### 1.29.5. Path Constraint

- Exact Path Constraint

- Child Node Constraint

- Find All Descendant Nodes

### 1.29.6. Ordering specifying

- Order by Property

- Order by Descendant Node Property

- Order by Score

- Order by Path or Name

### 1.29.7. Fulltext Search

- Fulltext Search by Property

- Fulltext Search by All Properties

- Find nt:file document by content of child jcr:content node

- How to set new Analyzer. Accent symblos ignoring

### 1.29.8. Indexing rules and additional features

- Aggregation rule

- Search Result Highlighting

- Index Boost Value

- Exclusion from the Node Scope Index

- Regular expressions as property name in indexing rule

- Synonim Provider

- Spell Checking

- Find Similar Nodes

### 1.29.9. Query Examples

#### SetOffset and SetLimit

Select all nodes with primary type 'nt:unstructured' and returns only 3 nodes starting with the second node in the list.

##### Common info

QueryImpl class has two methods: one to indicate how many results shall be returned at most, and another to fix the starting position.

- setOffset(long offset) - Sets the start offset of the result set.

- setLimit(long position) - Sets the maximum size of the result set.

##### Repository structure

Repository contains mix:title nodes, where jcr:title has different values.

- root

  - node1 (nt:unstructured)

  - node2 (nt:unstructured)

  - node3 (nt:unstructured)

  - node4 (nt:unstructured)

  - node5 (nt:unstructured)

  - node6 (nt:unstructured)

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:unstructured";

    QueryImpl query = (QueryImpl)queryManager.createQuery(sqlStatement, Query.SQL);

    //return starting with second result

    query.setOffset(1);

    // return 3 results

    query.setLimit(3);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

In usual case (without using setOffset and setLimit methods), Node iterator returns all nodes (node1...node6). But in our case NodeIterator will return "node2","node3" and "node4".

\[node1 node2 node3 node4 node5 node6\]

#### Finding All Nodes

Find all nodes in the repository. Only those nodes are found to which the session has READ permission. See also Access Control.

##### Repository structure:

Repository contains many different nodes.

- root

  - folder1 (nt:folder)

    - document1 (nt:file)

    - folder2 (nt:folder)

      - document2 (nt:unstructured)

      - document3 (nt:folder)

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:base";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,nt:base)";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "folder1", "folder2","document1","document2","document3", and each other nodes in workspace if they are here.

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is

jcr:path | jcr:score
--- | ---
/folder1 | 1000
/folder1/document1 | 1000
/folder1/folder2 | 1000
/folder1/folder2/document2 | 1000
/folder1/folder2/document3 | 1000
... | ...

#### Finding Nodes by Primary Type

Find all nodes whose primary type is "nt:file".

##### Repository structure:

The repository contains nodes with different primary types and mixin types.

- root

  - document1 primarytype = "nt:unstructured" mixintype = "mix:title"

  - document2 primarytype = "nt:file" mixintype = "mix:lockable"

  - document3 primarytype = "nt:file" mixintype = "mix:title"

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:file";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,nt:file)";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "document2" and "document3".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

The table content is

jcr:path | jcr:score
--- | ---
/document2 | 2674
/document3 | 2674

#### Finding Nodes by Mixin Type

Find all nodes in repository, that contain a mixin type "mix:title".

##### Repository structure:

The repository contains nodes with different primary types and mixin types.

- root

  - document1 primarytype = "nt:unstructured" mixintype = "mix:title"

  - document2 primarytype = "nt:file" mixintype = "mix:lockable"

  - document3 primarytype = "nt:file" mixintype = "mix:title"

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

The NodeIterator will return "document1" and "document3".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is

jcr:title | ... | jcr:path | jcr:score
--- | --- | --- | ---
First document | ... | /document1 | 2674
Second document | ... | /document3 | 2674

#### Property Comparison

Find all nodes with mixin type 'mix:title' where the prop_pagecount property contains a value less than 90. Only select the title of each node.

##### Repository structure:

Repository contains several mix:title nodes, where each prop_pagecount contains a different value.

- root

  - document1 (mix:title) jcr:title="War and peace" prop_pagecount=1000

  - document2 (mix:title) jcr:title="Cinderella" prop_pagecount=100

  - document3 (mix:title) jcr:title="Puss in Boots" prop_pagecount=60

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT jcr:title FROM mix:title WHERE prop_pagecount < 90";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)[@prop_pagecount < 90]/@jcr:title";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

The NodeIterator will return "document3".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

The table content is

jcr:title | jcr:path | jcr:score
--- | --- | ---
Puss in Boots | /document3 | 1725

#### LIKE Constraint

Find all nodes with mixin type 'mix:title' and where the property 'jcr:title' starts with 'P'.

### Note

See also the article about "Find all mix:title nodes where jcr:title does NOT start with 'P'"

##### Repository structure:

The repository contains 3 mix:title nodes, where each jcr:title has a different value.

- root

  - document1 (mix:title) jcr:title="Star wars" jcr:description="Dart rules!!"

  - document2 (mix:title) jcr:title="Prison break" jcr:description="Run, Forest, run ))"

  - document3 (mix:title) jcr:title="Panopticum" jcr:description="It's imagine film"

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title WHERE jcr:title LIKE 'P%'";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)[jcr:like(@jcr:title, 'P%')]";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

The NodeIterator will return "document2" and "document3".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

The table content is

jcr:title | jcr:description | jcr:path | jcr:score
--- | --- | --- | ---
Prison break | Run, Forest, run )) | /document2 | 4713
Panopticum | It's imagine film | /document3 | 5150

#### Escaping in LIKE Statements

Find all nodes with a mixin type 'mix:title' and whose property 'jcr:title' starts with 'P%ri'.

As you see "P%rison break" contains the symbol '%'. This symbol is reserved for LIKE comparisons. So what can we do?

Within the LIKE pattern, literal instances of percent ("%") or underscore ("_") must be escaped. The SQL ESCAPE clause allows the definition of an arbitrary escape character within the context of a single LIKE statement. The following example defines the backslash ' \' as escape character:

    SELECT * FROM mytype WHERE a LIKE 'foo\%' ESCAPE '\'

XPath does not have any specification for defining escape symbols, so we must use the default escape character (' \').

##### Repository structure

The repository contains mix:title nodes, where jcr:title can have different values.

- root

  - document1 (mix:title) jcr:title="Star wars" jcr:description="Dart rules!!"

  - document2 (mix:title) jcr:title="P%rison break" jcr:description="Run, Forest, run ))"

  - document3 (mix:title) jcr:title="Panopticum" jcr:description="It's imagine film"

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title WHERE jcr:title LIKE 'P#%ri%' ESCAPE '#'";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)[jcr:like(@jcr:title, 'P\\%ri%')]";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "document2".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

The table content is

jcr:title | jcr:description | jcr:path | jcr:score
--- | --- | --- | ---
P%rison break | Run, Forest, run )) | /document2 | 7452

#### NOT Constraint

Find all nodes with a mixin type 'mix:title' and where the property 'jcr:title' does NOT start with a 'P' symbol

##### Repository Structure

The repository contains a mix:title nodes, where the jcr:title has different values.

- root

  - document1 (mix:title) jcr:title="Star wars" jcr:description="Dart rules!!"

  - document2 (mix:title) jcr:title="Prison break" jcr:description="Run, Forest, run ))"

  - document3 (mix:title) jcr:title="Panopticum" jcr:description="It's imagine film"

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title WHERE NOT jcr:title LIKE 'P%'";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)[not(jcr:like(@jcr:title, 'P%'))]";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the result

Let's get the nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "document1".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is

jcr:title | jcr:description | jcr:path | jcr:score
--- | --- | --- | ---
Star wars | Dart rules!! | /document1 | 4713

#### AND Constraint

Find all fairytales with a page count more than 90 pages.

How does it sound in jcr terms - Find all nodes with mixin type 'mix:title' where the property 'jcr:description' equals "fairytale" and whose "prop_pagecount" property value is less than 90.

### Note

See also Multivalue Property Comparison.

##### Repository Structure:

The repository contains mix:title nodes, where prop_pagecount has different values.

- root

  - document1 (mix:title) jcr:title="War and peace" jcr:description="novel" prop_pagecount=1000

  - document2 (mix:title) jcr:title="Cinderella" jcr:description="fairytale" prop_pagecount=100

  - document3 (mix:title) jcr:title="Puss in Boots" jcr:description="fairytale" prop_pagecount=60

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title WHERE jcr:description = 'fairytale' AND prop_pagecount > 90";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)[@jcr:description='fairytale' and @prop_pagecount > 90]";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "document2".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:title | jcr:description | prop_pagecount | jcr:path | jcr:score
--- | --- | --- | --- | ---
Cinderella | fairytale | 100 | /document2 | 7086

#### OR Constraint

Find all documents whose title is 'Cinderella' or whose description is 'novel'.

How does it sound in jcr terms? - Find all nodes with a mixin type 'mix:title' whose property 'jcr:title' equals "Cinderella" or whose "jcr:description" property value is "novel".

##### Repository Structure

The repository contains mix:title nodes, where jcr:title and jcr:description have different values.

- root

  - document1 (mix:title) jcr:title="War and peace" jcr:description="novel"

  - document2 (mix:title) jcr:title="Cinderella" jcr:description="fairytale"

  - document3 (mix:title) jcr:title="Puss in Boots" jcr:description="fairytale"

##### Query Execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title WHERE jcr:title = 'Cinderella' OR jcr:description = 'novel'";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)[@jcr:title='Cinderella' or @jcr:description = 'novel']";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "document1" and "document2".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:title | jcr:description | jcr:path | jcr:score
--- | --- | --- | ---
War and peace | novel | /document1 | 3806
Cinderella | fairytale | /document2 | 3806

#### Property Existence Constraint

Find all nodes with a mixin type 'mix:title' where the property 'jcr:description' does not exist (is null).

##### Repository Structure

The repository contains mix:title nodes, in one of these nodes the jcr:description property is null.

- root

  - document1 (mix:title) jcr:title="Star wars" jcr:description="Dart rules!!"

  - document2 (mix:title) jcr:title="Prison break" jcr:description="Run, Forest, run ))"

  - document3 (mix:title) jcr:title="Titanic" // The description property does not exist. This is the node we wish to find.

##### Query Execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title WHERE jcr:description IS NULL";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = ""//element(*,mix:title)[not(@jcr:description)]"";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "document3".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:title | jcr:description | jcr:path | jcr:score
--- | --- | --- | ---
Titanic | null | /document3 | 1947

#### Finding Nodes in a Case-Insensitive Way

Find all nodes with a mixin type 'mix:title' and where the property 'jcr:title' equals 'casesensitive' in lower or upper case.

##### Repository Structure

The repository contains mix:title nodes, whose jcr:title properties have different values.

- root

  - document1 (mix:title) jcr:title="CaseSensitive"

  - document2 (mix:title) jcr:title="casesensitive"

  - document3 (mix:title) jcr:title="caseSENSITIVE"

##### Query Execution

- UPPER case

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title WHERE UPPER(jcr:title) = 'CASESENSITIVE'";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)[fn:upper-case(@jcr:title)='CASESENSITIVE']";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

- LOWER case

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title WHERE LOWER(jcr:title) = 'casesensitive'";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)[fn:lower-case(@jcr:title)='casesensitive']";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "document1", "document2" and "document3" (in all examples).

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:title | ... | jcr:path
--- | --- | ---
CaseSensitive | ... | /document1
casesensitive | ... | /document2
caseSENSITIVE | ... | /document3

#### Date Property Comparison

Find all nodes of primary type "nt:resource" whose jcr:lastModified property value is greater than 2006-06-04 and less than 2008-06-04.

##### Repository Structure

Repository contains nt:resource nodes with different values of jcr:lastModified property

- root

  - document1 (nt:file)

    - jcr:content (nt:resource) jcr:lastModified="2006-01-19T15:34:15.917+02:00"

  - document2 (nt:file)

    - jcr:content (nt:resource) jcr:lastModified="2005-01-19T15:34:15.917+02:00"

  - document3 (nt:file)

    - jcr:content (nt:resource) jcr:lastModified="2007-01-19T15:34:15.917+02:00"

##### Query Execution

SQL

In SQL you have to use the keyword TIMESTAMP for date comparisons. Otherwise, the date would be interpreted as a string. The date has to be surrounded by single quotes (TIMESTAMP 'datetime') and in the ISO standard format: YYYY-MM-DDThh:mm:ss.sTZD ( http://en.wikipedia.org/wiki/ISO_8601 and well explained in a W3C note http://www.w3.org/TR/NOTE-datetime).

You will see that it can be a date only (YYYY-MM-DD) but also a complete date and time with a timezone designator (TZD).

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    StringBuffer sb = new StringBuffer();

    sb.append("select * from nt:resource where ");

    sb.append("( jcr:lastModified >= TIMESTAMP '");

    sb.append("2006-06-04T15:34:15.917+02:00");

    sb.append("' )");

    sb.append(" and ");

    sb.append("( jcr:lastModified <= TIMESTAMP '");

    sb.append("2008-06-04T15:34:15.917+02:00");

    sb.append("' )");

    String sqlStatement = sb.toString();

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

Compared to the SQL format, you have to use the keyword xs:dateTime and surround the datetime by extra brackets: xs:dateTime('datetime'). The actual format of the datetime also conforms with the ISO date standard.

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    StringBuffer sb = new StringBuffer();

    sb.append("//element(*,nt:resource)");

    sb.append("[");

    sb.append("@jcr:lastModified >= xs:dateTime('2006-08-19T10:11:38.281+02:00')");

    sb.append(" and ");

    sb.append("@jcr:lastModified <= xs:dateTime('2008-06-04T15:34:15.917+02:00')");

    sb.append("]");

    String xpathStatement = sb.toString();

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node foundNode = it.nextNode();

    }

NodeIterator will return "/document3/jcr:content".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

The table content is:

jcr:lastModified | ... | jcr:path
--- | --- | ---
2007-01-19T15:34:15.917+02:00 | ... | /document3/jcr:content

#### Node Name Constraint

Find all nodes with primary type 'nt:file' whose node name is 'document'. The node name is accessible by a function called "fn:name()".

### Note

fn:name() can be used ONLY with an equal('=') comparison.

##### Repository Structure

The repository contains nt:file nodes with different names.

- root

  - document1 (nt:file)

  - file (nt:file)

  - somename (nt:file)

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:file WHERE fn:name() = 'document'";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,nt:file)[fn:name() = 'document']";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

The NodeIterator will return the node whose fn:name equals "document".

Also we can get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:path | jcr:score
--- | ---
/document1 | 3575

#### Multivalue Property Comparison

Find all nodes with the primary type 'nt:unstructured' whose property 'multiprop' contains both values "one" and "two".

##### Repository Structure

The repository contains nt:unstructured nodes with different 'multiprop' properties.

- root

  - node1 (nt:unstructured) multiprop = [ "one","two" ]

  - node1 (nt:unstructured) multiprop = [ "one","two","three" ]

  - node1 (nt:unstructured) multiprop = [ "one","five" ]

##### Query Execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:unstructured WHERE multiprop = 'one' AND multiprop = 'two'";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,nt:unstructured)[@multiprop = 'one' and @multiprop = 'two']";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

The NodeIterator will return "node1" and "node2".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:primarytyp | jcr:path | jcr:score
--- | --- | ---
nt:unstructured | /node1 | 3806
nt:unstructured | /node2 | 3806

#### Exact Path Constraint

Find a node with the primary type 'nt:file' that is located on the exact path "/folder1/folder2/document1".

##### Repository Structure

Repository filled by different nodes. There are several folders which contain other folders and files.

- root

  - folder1 (nt:folder)

    - folder2 (nt:folder)

      - document1 (nt:file) // This document we want to find

      - folder3 (nt:folder)

        - document1 (nt:file)

##### Query Execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // we want find 'document1'

    String sqlStatement = "SELECT * FROM nt:file WHERE jcr:path = '/folder1/folder2/document1'";

    // create query

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // we want to find 'document1'

    String xpathStatement = "/jcr:root/folder1[1]/folder2[1]/element(document1,nt:file)[1]";

    // create query

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

Remark: The indexes [1] are used in order to get the same result as the SQL statement. SQL by default only returns the first node, whereas XPath fetches by default all nodes.

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return expected "document1".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:path | jcr:score
--- | ---
/folder1/folder2/document1 | 1030

#### Child Node Constraint

Find all nodes with the primary type 'nt:folder' that are children of node by path "/root1/root2". Only find children, do not find further descendants.

##### Repository Structure

The repository is filled by "nt:folder" nodes. The nodes are placed in a multilayer tree.

- root

  - folder1 (nt:folder)

    - folder2 (nt:folder)

      - folder3 (nt:folder) // This node we want to find

        - folder4 (nt:folder) // This node is not child but a descendant of '/folder1/folder2/'.

      - folder5 (nt:folder) // This node we want to find

##### Query Execution

SQL

The use of "%" in the LIKE statement includes any string, therefore there is a second LIKE statement that excludes that the string contains "/". This way child nodes are included but descendant nodes are excluded.

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:folder WHERE jcr:path LIKE '/folder1/folder2/%' AND NOT jcr:path LIKE '/folder1/folder2/%/%'";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "/jcr:root/folder1[1]/folder2[1]/element(*,nt:folder)";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

The NodeIterator will return "folder3" and "folder5".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

The table content is:

jcr:path | jcr:score
--- | ---
/folder1/folder2/folder3 | 1707
/folder1/folder2/folder5 | 1707

#### Finding All Descendant Nodes

Find all nodes with the primary type 'nt:folder' that are descendants of the node "/folder1/folder2".

##### Repository Structure

The repository contains "nt:folder" nodes. The nodes are placed in a multilayer tree.

- root

  - folder1 (nt:folder)

    - folder2 (nt:folder)

      - folder3 (nt:folder) // This node we want to find

        - folder4 (nt:folder) // This node we want to find

      - folder5 (nt:folder) // This node we want to find

##### Query Execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:folder WHERE jcr:path LIKE '/folder1/folder2/%'";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "/jcr:root/folder1[1]/folder2[1]//element(*,nt:folder)";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

The NodeIterator will return "folder3", "folder4" and "folder5" nodes.

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:path | jcr:score
--- | ---
/folder1/folder2/folder3 | 1000
/folder1/folder2/folder3/folder4 | 1000
/folder1/folder2/folder5 | 1000

#### Sorting Nodes by Property

Select all nodes with the mixin type ''mix:title' and order them by the 'prop_pagecount' property.

##### Repository Structure

The repository contains several mix:title nodes, where prop_pagecount has different values.

- root

  - document1 (mix:title) jcr:title="War and peace" jcr:description="roman" prop_pagecount=4

  - document2 (mix:title) jcr:title="Cinderella" jcr:description="fairytale" prop_pagecount=7

  - document3 (mix:title) jcr:title="Puss in Boots" jcr:description="fairytale" prop_pagecount=1

##### Query Execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title ORDER BY prop_pagecount ASC";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title) order by @prop_pagecount ascending";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

The NodeIterator will return nodes in the following order "document3", "document1", "document2".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:title | jcr:description | prop_pagecount | jcr:path | jcr:score
--- | --- | --- | --- | ---
Puss in Boots | fairytale | 1 | /document3 | 1405
War and peace | roman | 4 | /document1 | 1405
Cinderella | fairytale | 7 | /document2 | 1405

#### Ordering by Descendant Nodes Property (XPath only)

Find all nodes with the primary type 'nt:unstructured' and sort them by the property value of descendant nodes with the relative path '/a/b'.

### Note

This ORDER BY construction only works in XPath!

##### Repository structure:

- root

  - node1 (nt:unstructured)

    - a (nt:unstructured)

      - b (nt:unstructured)

  - node2 (nt:unstructured)

    - a (nt:unstructured)

      - b (nt:unstructured)

        - c (nt:unstructured) prop = "a"

  - node3 (nt:unstructured)

    - a (nt:unstructured)

      - b (nt:unstructured)

        - c (nt:unstructured) prop = "b"

##### Query Execution

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "/jcr:root/* order by a/b/c/@prop descending;

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return nodes in the following order - "node3","node2" and "node1".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:primaryType | jcr:path | jcr:score
--- | --- | ---
nt:unstructured | /testroot/node3 | 1000
nt:unstructured | /testroot/node2 | 1000
nt:unstructured | /testroot/node1 | 1000

#### Ordering by Score

Select all nodes with the mixin type 'mix:title' containing any word from the set {'brown','fox','jumps'}. Then, sort result by the score in ascending node. This way nodes that match better the query statement are ordered at the last positions in the result list.

##### Info

SQL and XPath queries support both score constructions jcr:score and jcr:score()

    SELECT * FROM nt:base ORDER BY jcr:score [ASC|DESC]
    SELECT * FROM nt:base ORDER BY jcr:score()[ASC|DESC]

    //element(*,nt:base) order by jcr:score() [descending]
    //element(*,nt:base) order by @jcr:score [descending]

Do not use "ascending" combined with jcr:score in XPath. The following XPath statement may throw an exception:

    ... order by jcr:score() ascending

Do not set any ordering specifier - ascending is default:

    ... order by jcr:score()

##### Repository Structure

The repository contains mix:title nodes, where the jcr:description has different values.

- root

  - document1 (mix:title) jcr:description="The quick brown fox jumps over the lazy dog."

  - document2 (mix:title) jcr:description="The brown fox lives in the forest."

  - document3 (mix:title) jcr:description="The fox is a nice animal."

##### Query Execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title WHERE CONTAINS(*, 'brown OR fox OR jumps') ORDER BY jcr:score() ASC";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)[jcr:contains(., 'brown OR fox OR jumps')] order by jcr:score()";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return nodes in the following order: "document3", "document2", "document1".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:description | ... | jcr:path | jcr:score
--- | --- | --- | ---
The fox is a nice animal. | ... | /document3 | 2512
The brown fox lives in the forest. | ... | /document2 | 3595
The quick brown fox jumps over the lazy dog. | ... | /document1 | 5017

#### Ordering by Path or Name

### Warning

Ordering by jcr:path or jcr:name does not supported.

There is two ways to order results, when path may be used as criteria:

- Order by property with value type NAME or PATH (jcr supports it)

- Order by jcr:path or jcr:name - sort by exact path or name of node (jcr do not supports it)

If no order specification is supplied in the query statement, implementations may support document order on the result nodes (see jsr-170 / 6.6.4.2 Document Order). And it's sorted by order number.

By default, (if query do not contains any ordering statements) result nodes is sorted by document order.

    SELECT * FROM nt:unstructured WHERE jcr:path LIKE 'testRoot/%'

#### Fulltext Search by Property

Find all nodes containing a mixin type 'mix:title' and whose 'jcr:description' contains "forest" string.

##### Repository Structure

The repository is filled with nodes of the mixin type 'mix:title' and different values of the 'jcr:description' property.

- root

  - document1 (mix:title) jcr:description = "The quick brown fox jumps over the lazy dog."

  - document2 (mix:title) jcr:description = "The brown fox lives in a forest." // This is the node we want to find

  - document3 (mix:title) jcr:description = "The fox is a nice animal."

  - document4 (nt:unstructured) jcr:description = "There is the word forest, too."

##### Query Execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // we want find document which contains "forest" word

    String sqlStatement = "SELECT \* FROM mix:title WHERE CONTAINS(jcr:description, 'forest')";

    // create query

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // we want find document which contains "forest" word

    String xpathStatement = "//element(*,mix:title)[jcr:contains(@jcr:description, 'forest')]";

    // create query

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the Result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "document2".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:description | ... | jcr:path
--- | --- | ---
The brown fox lives in forest. | ... | /document2

#### Fulltext Search by All Properties in Node

Find nodes with mixin type 'mix:title' where any property contains 'break' string.

##### Repository structure:

Repository filled with different nodes with mixin type 'mix:title' and different values of 'jcr:title' and 'jcr:description' properties.

- root

  - document1 (mix:title) jcr:title ='Star Wars' jcr:description = 'Dart rules!!'

  - document2 (mix:title) jcr:title ='Prison break' jcr:description = 'Run, Forest, run ))'

  - document3 (mix:title) jcr:title ='Titanic' jcr:description = 'An iceberg breaks a ship.'

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    String sqlStatement = "SELECT * FROM mix:title WHERE CONTAINS(*,'break')";

    // create query

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // we want find 'document1'

    String xpathStatement = "//element(*,mix:title)[jcr:contains(.,'break')]";

    // create query

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    while(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "document1" and "document2".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:title | jcr:description | ... | jcr:path
--- | --- | --- | ---
Prison break. | Run, Forest, run )) | ... | /document2
Titanic | An iceberg breaks a ship. | ... | /document3

#### Ignoring Accent Symbols. New Analyzer Setting.

In this example, we will create new Analyzer, set it in QueryHandler configuration, and make query to check it.

Standard analyzer does not normalize accents like é,è,à. So, a word like 'tréma' will be stored to index as 'tréma'. But if we want to normalize such symbols or not? We want to store 'tréma' word as 'trema'.

There is two ways of setting up new Analyzer (no matter standarts or our):

- The first way: Create descendant class of SearchIndex with new Analyzer (see Search Configuration);

There is only one way - create new Analyzer (if there is no previously created and accepted for our needs) and set it in Search index.

- The second way: Register new Analyzer in QueryHandler configuration (this one eccepted since 1.12 version);

We will use the last one:

- Create new MyAnalyzer

    public class MyAnalyzer extends Analyzer

    {

       @Override

       public TokenStream tokenStream(String fieldName, Reader reader)

       {

          StandardTokenizer tokenStream = new StandardTokenizer(reader);

          // process all text with standard filter

          // removes 's (as 's in "Peter's") from the end of words and removes dots from acronyms.

          TokenStream result = new StandardFilter(tokenStream);

          // this filter normalizes token text to lower case

          result = new LowerCaseFilter(result);

          // this one replaces accented characters in the ISO Latin 1 character set (ISO-8859-1) by their unaccented equivalents

          result = new ISOLatin1AccentFilter(result);

          // and finally return token stream

          return result;

       }

    }

- Then, register new MyAnalyzer in configuration

    <workspace name="ws">

       ...

       <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

          <properties>

             <property name="analyzer" value="org.exoplatform.services.jcr.impl.core.MyAnalyzer"/>

             ...

          </properties>

       </query-handler>

       ...

    </workspace>

After that, check it with query:

Find node with mixin type 'mix:title' where 'jcr:title' contains "tréma" and "naïve" strings.

##### Repository structure:

Repository filled by nodes with mixin type 'mix:title' and different values of 'jcr:title' property.

- root

  - node1 (mix:title) jcr:title = "tréma blabla naïve"

  - node2 (mix:title) jcr:description = "trema come text naive"

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title WHERE CONTAINS(jcr:title, 'tr\u00E8ma na\u00EFve')";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)[jcr:contains(@jcr:title, 'tr\u00E8ma na\u00EFve')]";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "node1" and "node2". How is it possible? Remember that our MyAnalyzer transforms 'tréma' word to 'trema'. So node2 accepts our constraints to.

Also, we can get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is

cr:title | ... | cr:path
--- | --- | ---
trèma blabla naïve | ... | /node1
trema come text naive | ... | /node2

#### Finding nt:file node by content of child jcr:content node

The node type nt:file represents a file. It requires a single child node, called jcr:content. This node type represents images and other binary content in a JCRWiki entry. The node type of jcr:conent is nt:resource which represents the actual content of a file.

Find node with the primary type is 'nt:file' and which whose 'jcr:content' child node contains "cats".

Normally, we can't find nodes (in our case) using just JCR SQL or XPath queries. But we can configure indexing so that nt:file aggregates jcr:content child node.

So, change indexing-configuration.xml:

    <?xml version="1.0"?>

    <!DOCTYPE configuration SYSTEM "http://www.exoplatform.org/dtd/indexing-configuration-1.2.dtd">

    <configuration xmlns:jcr="http://www.jcp.org/jcr/1.0"

                   xmlns:nt="http://www.jcp.org/jcr/nt/1.0">

        <aggregate primaryType="nt:file">

            <include>jcr:content</include>

            <include>jcr:content/*</include>

            <include-property>jcr:content/jcr:lastModified</include-property>

        </aggregate>

    </configuration>

Now the content of 'nt:file' and 'jcr:content' ('nt:resource') nodes are concatenated in a single Lucene document. Then, we can make a fulltext search query by content of 'nt:file'; this search includes the content of child 'jcr:content' node.

##### Repository structure:

Repository contains different nt:file nodes.

- root

  - document1 (nt:file)

    - jcr:content (nt:resource) jcr:data = "The quick brown fox jumps over the lazy dog."

  - document2 (nt:file)

    - jcr:content (nt:resource) jcr:data = "Dogs do not like cats."

  - document3 (nt:file)

    - jcr:content (nt:resource) jcr:data = "Cats jumping high."

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:file WHERE CONTAINS(*,'cats')";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,nt:file)[jcr:contains(.,'cats')]";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching the result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "document2" and "document3".

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:path | jcr:score
--- | ---
/document2 | 1030
/document3 | 1030

#### Changing Priority of Node

In this example, we will set different boost values for predefined nodes, and will check effect by selecting those nodes and order them by jcr:score.

The default boost value is 1.0. Higher boost values (a reasonable range is 1.0 - 5.0) will yield a higher score value and appear as more relevant.

### Note

See 4.2.2 Index Boost Value Search Configuration

##### Indexing configuration

In next configuration, we will set boost values for nt:ustructured nodes 'text' property.

indexing-config.xml:

    <!--

    This rule actualy do nothing. 'text' property has default boost value.

    -->

    <index-rule nodeType="nt:unstructured" condition="@rule='boost1'">

       <!-- default boost: 1.0 -->

       <property>text</property>

    </index-rule>

    <!--

    Set boost value as 2.0 for 'text' property in nt:unstructured nodes where property 'rule' equal to 'boost2'

    -->

    <index-rule nodeType="nt:unstructured" condition="@rule='boost2'">

       <!-- boost: 2.0 -->

       <property boost="2.0">text</property>

    </index-rule>

    <!--

    Set boost value as 3.0 for 'text' property in nt:unstructured nodes where property 'rule' equal to 'boost3'

    -->

    <index-rule nodeType="nt:unstructured" condition="@rule='boost3'">

       <!-- boost: 3.0 -->

       <property boost="3.0">text</property>

    </index-rule>

##### Repository structure:

Repository contains many nodes with primary type nt:unstructured. Each node contains 'text' property and 'rule' property with different values.

- root

  - node1(nt:unstructured) rule='boost1' text='The quick brown fox jump...'

  - node2(nt:unstructured) rule='boost2' text='The quick brown fox jump...'

  - node3(nt:unstructured) rule='boost3' text='The quick brown fox jump...'

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:unstructured WHERE CONTAINS(text, 'quick') ORDER BY jcr:score() DESC";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,nt:unstructured)[jcr:contains(@text, 'quick')] order by @jcr:score descending";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return nodes in next order "node3", "node2", "node1".

#### Removing Nodes Property From Indexing Scope

In this example, we will exclude some 'text' property of nt:unstructured node from indexind. And, therefore, node will not be found by the content of this property, even if it accepts all constraints.

First of all, add rules to indexing-configuration.xml:

    <index-rule nodeType="nt:unstructured" condition="@rule='nsiTrue'">

        <!-- default value for nodeScopeIndex is true -->

        <property>text</property>

    </index-rule>

    <index-rule nodeType="nt:unstructured" condition="@rule='nsiFalse'">

        <!-- do not include text in node scope index -->

        <property nodeScopeIndex="false">text</property>

    </index-rule>

### Note

See Search Configuration

##### Repository structure:

Repository contains nt:unstructured nodes, with same 'text'property and different 'rule' properties (even null)

- root

  - node1 (nt:unstructured) rule="nsiTrue" text="The quick brown fox ..."

  - node2 (nt:unstructured) rule="nsiFalse" text="The quick brown fox ..."

  - node3 (nt:unstructured) text="The quick brown fox ..." // as you see this node not mentioned in indexing-coniguration

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:unstructured WHERE CONTAINS(*,'quick')";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,nt:unstructured)[jcr:contains(., 'quick')]";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "node1" and "node3". Node2, as you see, is not in result set.

Also, we can get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is

jcr:primarytype | jcr:path | jcr:score
--- | --- | ---
nt:unstructured | /node1 | 3806
nt:unstructured | /node3 | 3806

#### Regular Expression as Property Name in Indexing Rules

In this example, we want to configure indexind in the next way. All properties of nt:unstructured nodes must be excluded from search, except properties whoes names ends with 'Text' string. First of all, add rules to indexing-configuration.xml:

    <index-rule nodeType="nt:unstructured"">

       <property isRegexp="true">.*Text</property>

    </index-rule>

### Note

See Search Configuration

Now, let's check this rule with simple query - select all nodes with primary type 'nt:unstructured' and containing 'quick' string (fulltext search by full node).

##### Repository structure:

Repository contains nt:unstructured nodes, with different 'text'-like named properties

- root

  - node1 (nt:unstructured) Text="The quick brown fox ..."

  - node2 (nt:unstructured) OtherText="The quick brown fox ..."

  - node3 (nt:unstructured) Textle="The quick brown fox ..."

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:unstructured WHERE CONTAINS(*,'quick')";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,nt:unstructured)[jcr:contains(., 'quick')]";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "node1" and "node2". "node3", as you see, is not in result set.

Also, we can get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is:

jcr:primarytype | jcr:path | jcr:score
--- | --- | ---
nt:unstructured | /node1 | 3806
nt:unstructured | /node2 | 3806

#### High-lighting Result of Fulltext Search

It's also called excerption (see Excerpt configuration in Search Configuration and in Searching Repository article).

The goal of this query is to find words "eXo" and "implementation" with fulltext search and high-light this words in result value.

##### Base info

High-lighting is not default feature so we must set it in jcr-config.xml, also excerpt provider must be defined:

    <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

       <properties>

          ...

          <property name="support-highlighting" value="true" />

          <property name="excerptprovider-class" value="org.exoplatform.services.jcr.impl.core.query.lucene.WeightedHTMLExcerpt"/>

          ...

       <properties>

    </query-handler>

Also, remember that we can make indexing rules, as in the example below:

Let's write rule for all nodes with primary node type 'nt:unstructed' where property 'rule' equal to "excerpt" string. For those nodes, we will exclude property "title" from high-lighting and set "text" property as highlightable. Indexing-configuration.xml must containt the next rule:

    <index-rule nodeType="nt:unstructured" condition="@rule='excerpt'">

       <property useInExcerpt="false">title</property>

       <property>text</property>

    </index-rule>

##### Repository structure:

We have single node with primary type 'nt:unstructured'

- document (nt:unstructured)

  - rule = "excerpt"

  - title = "eXoJCR"

  - text = "eXo is a JCR implementation"

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT rep:excerpt() FROM nt:unstructured WHERE CONTAINS(*, 'eXo implementation')";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,nt:unstructured)[jcr:contains(., 'eXo implementation')]/rep:excerpt(.)";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Now let's see on the result table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

Table content is

rep:excerpt() | jcr:path | jcr:score
--- | --- | ---
\<div\>\<span\>\<strong\>eXo\</strong\> is a JCR \<strong\>implementation\</strong\>\</span\>\</div\> | /testroot/node1 | 335

As you see, words "eXo" and "implamentation" is highlighted.

Also, we can get exactly "rep:excerpt" value:

    RowIterator rows = result.getRows();

    Value excerpt = rows.nextRow().getValue("rep:excerpt(.)");

    // excerpt will be equal to "<div><span\><strong>eXo</strong> is a JCR <strong>implementation</strong></span></div>"

#### Searching By Synonim

Find all mix:title nodes where title contains synonims to 'fast' word.

### Note

See also about synonim propvider configuration - Searching Repository Content

Synonim provider must be configured in indexing-configuration.xml :

    <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

       <properties>

          ...

          <property name="synonymprovider-class" value="org.exoplatform.services.jcr.impl.core.query.lucene.PropertiesSynonymProvider" />

          <property name="synonymprovider-config-path" value="../../synonyms.properties" />

          ...

       </properties>

    </query-handler>

File synonim.properties contains next synonims list:

    ASF=Apache Software Foundation
    quick=fast
    sluggish=lazy

##### Repository structure:

Repository contains mix:title nodes, where jcr:title has different values.

- root

  - document1 (mix:title) jcr:title="The quick brown fox jumps over the lazy dog."

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM mix:title WHERE CONTAINS(jcr:title, '~fast')";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*,mix:title)[jcr:contains(@jcr:title, '~fast')]";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return expected document1. This is a purpose of synonim providers. Find by specified word, but return by all synonims to.

#### Checking the spelling of Phrase

Check the correct spelling of phrase 'quik OR (-foo bar)' according to data already stored in index.

### Note

See also about SpellChecker configuration - Searching Repository Content

SpellChecker must be settled in query-handler config.

test-jcr-config.xml:

    <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

       <properties>

          ...

       <property name="spellchecker-class" value="org.exoplatform.services.jcr.impl.core.query.lucene.spell.LuceneSpellChecker$FiveSecondsRefreshInterval" />

          ...

       </properties>

    </query-handler>

##### Repository structure:

Repository contains node, with string property "The quick brown fox jumps over the lazy dog."

- root

  - node1 property="The quick brown fox jumps over the lazy dog."

##### Query execution

Query looks only for root node, because spell checker looks for suggestions by full index. So complicated query is redundant.

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT rep:spellcheck() FROM nt:base WHERE jcr:path = '/' AND SPELLCHECK('quik OR (-foo bar)')";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "/jcr:root[rep:spellcheck('quik OR (-foo bar)')]/(rep:spellcheck())";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Get suggestion of coorect spelling our phrase:

    RowIterator it = result.getRows();

    Row r = rows.nextRow();

    Value v = r.getValue("rep:spellcheck()");

    String correctPhrase = v.getString();

So, correct spelling for phrase "quik OR (-foo bar)" is "quick OR (-fox bar)".

#### Finding Similar Nodes

Find similar nodes to node by path '/baseFile/jcr:content'.

In our example, baseFile will contain text where "terms" word happens many times. That's a reason why the existanse of this word will be used as a criteria of node similarity (for node baseFile).

### Note

See also about Similarity and configuration - Searching Repository Content

Higlighting support must be added to configuration. test-jcr-config.xml:

    <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">

       <properties>

          ...

          <property name="support-highlighting" value="true" />

          ...

       </properties>

    </query-handler>

##### Repository structure:

Repository contains many nt:file nodes"

- root

  - baseFile (nt:file)

    - jcr:content (nt:resource) jcr:data="Similarity is determined by looking up terms that are common to nodes. There are some conditions that must be met for a term to be considered. This is required to limit the number possibly relevant terms. Only terms with at least 4 characters are considered. Only terms that occur at least 2 times in the source node are considered. Only terms that occur in at least 5 nodes are considered."

  - target1 (nt:file)

    - jcr:content (nt:resource) jcr:data="Similarity is determined by looking up terms that are common to nodes."

  - target2 (nt:file)

    - jcr:content (nt:resource) jcr:data="There is no you know what"

  - target3 (nt:file)

    - jcr:content (nt:resource) jcr:data=" Terms occures here"

##### Query execution

SQL

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String sqlStatement = "SELECT * FROM nt:resource WHERE SIMILAR(.,'/baseFile/jcr:content')";

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

XPath

    // make XPath query

    QueryManager queryManager = workspace.getQueryManager();

    // create query

    String xpathStatement = "//element(*, nt:resource)[rep:similar(., '/testroot/baseFile/jcr:content')]";

    Query query = queryManager.createQuery(xpathStatement, Query.XPATH);

    // execute query and fetch result

    QueryResult result = query.execute();

##### Fetching result

Let's get nodes:

    NodeIterator it = result.getNodes();

    if(it.hasNext())

    {

       Node findedNode = it.nextNode();

    }

NodeIterator will return "/baseFile/jcr:content","/target1/jcr:content" and "/target3/jcr:content".

As you see the base node are also in result set.

We can also get a table:

    String[] columnNames = result.getColumnNames();

    RowIterator rit = result.getRows();

    while (rit.hasNext())

    {

       Row row = rit.nextRow();

       // get values of the row

       Value[] values = row.getValues();

    }

The table content is

jcr:path | ... | jcr:score
--- | --- | ---
/baseFile/jcr:content | ... | 2674
/target1/jcr:content | ... | 2674
/target3/jcr:content | ... | 2674

### 1.29.10. Tips and tricks

#### XPath queries containing node names starting with a number

If you execute an XPath request like this:

XPath

    // get QueryManager

    QueryManager queryManager = workspace.getQueryManager();

    // make XPath query

    Query query = queryManager.createQuery("/jcr:root/Documents/Publie/2010//element(*, exo:article)", Query.XPATH);

You will have an error : "Invalid request". This happens because XML does not allow names starting with a number - and XPath is part of XML: http://www.w3.org/TR/REC-xml/#NT-Name

Therefore, you cannot do XPath requests using a node name that starts with a number.

Easy workarounds:

- Use an SQL request.

- Use escaping :

XPath

    // get QueryManager

    QueryManager queryManager = workspace.getQueryManager();

    // make XPath query

    Query query = queryManager.createQuery("/jcr:root/Documents/Publie/_x0032_010//element(*, exo:article)", Query.XPATH);

## 1.30. Searching Repository Content

You can find the JCR configuration file at .../portal/WEB-INF/conf/jcr/repository-configuration.xml. Please read also Search Configuration for more information about index configuration.

### 1.30.1. Bi-directional RangeIterator (since 1.9)

QueryResult.getNodes() will return bi-directional NodeIterator implementation.

### Note

Bi-directional NodeIterator is not supported in two cases:

- SQL query: select * from nt:base

- XPath query: //* .

TwoWayRangeIterator interface:

    /**

     * Skip a number of elements in the iterator.

     *

     * @param skipNum the non-negative number of elements to skip

     * @throws java.util.NoSuchElementException if skipped past the first element

     *           in the iterator.

     */

    public void skipBack(long skipNum);

Usage:

    NodeIterator iter = queryResult.getNodes();

    while (iter.hasNext()) {

      if (skipForward) {

        iter.skip(10); // Skip 10 nodes in forward direction

      } else if (skipBack) {

        TwoWayRangeIterator backIter = (TwoWayRangeIterator) iter;

        backIter.skipBack(10); // Skip 10 nodes back

      }

      .......

    }

### 1.30.2. Fuzzy Searches (since 1.0)

JCR supports such features as Lucene Fuzzy Searches Apache Lucene - Query Parser Syntax.

To use it, you have to form a query like the one described below:

    QueryManager qman = session.getWorkspace().getQueryManager();

    Query q = qman.createQuery("select * from nt:base where contains(field, 'ccccc~')", Query.SQL);

    QueryResult res = q.execute();

### 1.30.3. SynonymSearch (since 1.9)

Searching with synonyms is integrated in the jcr:contains() function and uses the same syntax as synonym searches in Google. If a search term is prefixed by a tilde symbol ( ~ ), also synonyms of the search term are taken into consideration. For example:

    SQL: select * from nt:resource where contains(., '~parameter')

    XPath: //element(*, nt:resource)[jcr:contains(., '~parameter')

This feature is disabled by default and you need to add a configuration parameter to the query-handler element in your jcr configuration file to enable it.

    <param  name="synonymprovider-config-path" value="..you path to configuration file....."/>

    <param  name="synonymprovider-class" value="org.exoplatform.services.jcr.impl.core.query.lucene.PropertiesSynonymProvider"/>

    /**

     * <code>SynonymProvider</code> defines an interface for a component that

     * returns synonyms for a given term.

     */

    public interface SynonymProvider {

       /**

        * Initializes the synonym provider and passes the file system resource to

        * the synonym provider configuration defined by the configuration value of

        * the <code>synonymProviderConfigPath</code> parameter. The resource may be

        * <code>null</code> if the configuration parameter is not set.

        *

        * @param fsr the file system resource to the synonym provider

        *            configuration.

        * @throws IOException if an error occurs while initializing the synonym

        *                     provider.

        */

       public void initialize(InputStream fsr) throws IOException;

       /**

        * Returns an array of terms that are considered synonyms for the given

        * <code>term</code>.

        *

        * @param term a search term.

        * @return an array of synonyms for the given <code>term</code> or an empty

        *         array if no synonyms are known.

        */

       public String[] getSynonyms(String term);

    }

### 1.30.4. High-lighting (Since 1.9)

An ExcerptProvider retrieves text excerpts for a node in the query result and marks up the words in the text that match the query terms.

By default highlighting words matched the query is disabled because this feature requires that additional information is written to the search index. To enable this feature, you need to add a configuration parameter to the query-handler element in your jcr configuration file to enable it.

    <param name="support-highlighting" value="true"/>

Additionally, there is a parameter that controls the format of the excerpt created. In JCR 1.9, the default is set to org.exoplatform.services.jcr.impl.core.query.lucene.DefaultHTMLExcerpt. The configuration parameter for this setting is:

    <param name="excerptprovider-class" value="org.exoplatform.services.jcr.impl.core.query.lucene.DefaultXMLExcerpt"/>

#### DefaultXMLExcerpt

This excerpt provider creates an XML fragment of the following form:

    <excerpt>

        <fragment>

            <highlight>exoplatform</highlight> implements both the mandatory

            XPath and optional SQL <highlight>query</highlight> syntax.

        </fragment>

        <fragment>

            Before parsing the XPath <highlight>query</highlight> in

            <highlight>exoplatform</highlight>, the statement is surrounded

        </fragment>

    </excerpt>

#### DefaultHTMLExcerpt

This excerpt provider creates an HTML fragment of the following form:

    <div>

        <span>

            <strong>exoplatform</strong> implements both the mandatory XPath

            and optional SQL <strong>query</strong> syntax.

        </span>

        <span>

            Before parsing the XPath <strong>query</strong> in

            <strong>exoplatform</strong>, the statement is surrounded

        </span>

    </div>

#### How to use it

If you are using XPath, you must use the rep:excerpt() function in the last location step, just like you would select properties:

    QueryManager qm = session.getWorkspace().getQueryManager();

    Query q = qm.createQuery("//*[jcr:contains(., 'exoplatform')]/(@Title|rep:excerpt(.))", Query.XPATH);

    QueryResult result = q.execute();

    for (RowIterator it = result.getRows(); it.hasNext(); ) {

       Row r = it.nextRow();

       Value title = r.getValue("Title");

       Value excerpt = r.getValue("rep:excerpt(.)");

    }

The above code searches for nodes that contain the word exoplatform and then gets the value of the Title property and an excerpt for each result node.

It is also possible to use a relative path in the call Row.getValue() while the query statement still remains the same. Also, you may use a relative path to a string property. The returned value will then be an excerpt based on string value of the property.

Both available excerpt provider will create fragments of about 150 characters and up to 3 fragments.

In SQL, the function is called excerpt() without the rep prefix, but the column in the RowIterator will nonetheless be labled rep:excerpt(.)!

    QueryManager qm = session.getWorkspace().getQueryManager();

    Query q = qm.createQuery("select excerpt(.) from nt:resource where contains(., 'exoplatform')", Query.SQL);

    QueryResult result = q.execute();

    for (RowIterator it = result.getRows(); it.hasNext(); ) {

       Row r = it.nextRow();

       Value excerpt = r.getValue("rep:excerpt(.)");

    }

### 1.30.5. SpellChecker

The lucene based query handler implementation supports a pluggable spell checker mechanism. By default, spell checking is not available and you have to configure it first. See parameter spellCheckerClass on page Search Configuration. JCR currently provides an implementation class , which uses the lucene-spellchecker to contribute . The dictionary is derived from the fulltext indexed content of the workspace and updated periodically. You can configure the refresh interval by picking one of the available inner classes of org.exoplatform.services.jcr.impl.core.query.lucene.spell.LuceneSpellChecker:

- OneMinuteRefreshInterval

- FiveMinutesRefreshInterval

- ThirtyMinutesRefreshInterval

- OneHourRefreshInterval

- SixHoursRefreshInterval

- TwelveHoursRefreshInterval

- OneDayRefreshInterval

For example, if you want a refresh interval of six hours, the class name is: org.exoplatform.services.jcr.impl.core.query.lucene.spell.LuceneSpellChecker$SixHoursRefreshInterval. If you use org.exoplatform.services.jcr.impl.core.query.lucene.spell.LuceneSpellChecker, the refresh interval will be one hour.

The spell checker dictionary is stored as a lucene index under "index-dir"/spellchecker. If it does not exist, a background thread will create it on startup. Similarly, the dictionary refresh is also done in a background thread to not block regular queries.

#### How do I use it?

You can spell check a fulltext statement either with an XPath or a SQL query:

    // rep:spellcheck('explatform') will always evaluate to true

    Query query = qm.createQuery("/jcr:root[rep:spellcheck('explatform')]/(rep:spellcheck())", Query.XPATH);

    RowIterator rows = query.execute().getRows();

    // the above query will always return the root node no matter what string we check

    Row r = rows.nextRow();

    // get the result of the spell checking

    Value v = r.getValue("rep:spellcheck()");

    if (v == null) {

       // no suggestion returned, the spelling is correct or the spell checker

       // does not know how to correct it.

    } else {

       String suggestion = v.getString();

    }

And the same using SQL:

    // SPELLCHECK('exoplatform') will always evaluate to true

    Query query = qm.createQuery("SELECT rep:spellcheck() FROM nt:base WHERE jcr:path = '/' AND SPELLCHECK('explatform')", Query.SQL);

    RowIterator rows = query.execute().getRows();

    // the above query will always return the root node no matter what string we check

    Row r = rows.nextRow();

    // get the result of the spell checking

    Value v = r.getValue("rep:spellcheck()");

    if (v == null) {

       // no suggestion returned, the spelling is correct or the spell checker

       // does not know how to correct it.

    } else {

       String suggestion = v.getString();

    }

### 1.30.6. Similarity (Since 1.12)

Starting with version, 1.12 JCR allows you to search for nodes that are similar to an existing node.

Similarity is determined by looking up terms that are common to nodes. There are some conditions that must be met for a term to be considered. This is required to limit the number possibly relevant terms.

- Only terms with at least 4 characters are considered.

- Only terms that occur at least 2 times in the source node are considered.

- Only terms that occur in at least 5 nodes are considered.

Note: The similarity functionality requires that the support Hightlighting is enabled. Please make sure that you have the following parameter set for the query handler in your workspace.xml.

    <param name="support-highlighting" value="true"/>

The functions are called rep:similar() (in XPath) and similar() (in SQL) and have two arguments:

relativePath: a relative path to a descendant node or . for the current node. absoluteStringPath: a string literal that contains the path to the node for which to find similar nodes.

### Warning

Relative path is not supported yet.

Examples:

    //element(*, nt:resource)[rep:similar(., '/parentnode/node.txt/jcr:content')]

Finds nt:resource nodes, which are similar to node by path /parentnode/node.txt/jcr:content.

## 1.31. Fulltext Search And Affecting Settings

### 1.31.1. Property content indexing

Each property of a node (if it is indexable) is processed with Lucene analyzer and stored in Lucene index. That's called indexing of a property. After that we can perform a fulltext search among these indexed properties.

### 1.31.2. Lucene Analyzers

The sense of analyzers is to transform all strings stored in the index in a well-defined condition. The same analyzer(s) is/are used when searching in order to adapt the query string to the index reality.

Therefore, performing the same query using different analyzers can return different results.

Now, let's see how the same string is transformed by different analyzers.

Analyzer | Parsed
--- | ---
org.apache.lucene.analysis.WhitespaceAnalyzer | [The] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs]
org.apache.lucene.analysis.SimpleAnalyzer | [the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs]
org.apache.lucene.analysis.StopAnalyzer | [quick] [brown] [fox] [jumped] [over] [lazy] [dogs]
org.apache.lucene.analysis.standard.StandardAnalyzer | [quick] [brown] [fox] [jumped] [over] [lazy] [dogs]
org.apache.lucene.analysis.snowball.SnowballAnalyzer | [quick] [brown] [fox] [jump] [over] [lazi] [dog]
org.apache.lucene.analysis.standard.StandardAnalyzer (configured without stop word - jcr default analyzer) | [the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs]

Analyzer | Parsed
--- | ---
org.apache.lucene.analysis.WhitespaceAnalyzer | [XY&Z] [Corporation] [-] [xyz@example.com]
org.apache.lucene.analysis.SimpleAnalyzer | [xy] [z] [corporation] [xyz] [example] [com]
org.apache.lucene.analysis.StopAnalyzer | [xy] [z] [corporation] [xyz] [example] [com]
org.apache.lucene.analysis.standard.StandardAnalyzer | [xy&z] [corporation] [xyz@example] [com]
org.apache.lucene.analysis.snowball.SnowballAnalyzer | [xy&z] [corpor] [xyz@exampl] [com]
org.apache.lucene.analysis.standard.StandardAnalyzer (configured without stop word - jcr default analyzer) | [xy&z] [corporation] [xyz@example] [com]

### Note

StandardAnalyzer is the default analyzer in exo's jcr search engine. But we do not use stop words.

You can assign your analyzer as described in Search Configuration

### 1.31.3. How are different properties indexed?

Different properties are indexed in different ways, this affects to if it can be searched like fulltext by property or not.

Only two property types are indexed as fulltext searcheable: STRING and BINARY.

Property Type | Fulltext search by all properties | Fulltext search by exact property
--- | --- | ---
STRING | YES | YES
BINARY | YES | NO

For example, ưe have property jcr:data (it' BINARY). It's stored well, but you will never find any string with query like:

    SELECT * FROM nt:resource WHERE CONTAINS(jcr:data, 'some string')

Because, BINARY is not searchable by fulltext search on exact property.

But, next query will return result (off course if node has searched data):

    SELECT * FROM nt:resource WHERE CONTAINS( * , 'some string')

### 1.31.4. Fulltext search query examples

- JCR.Fulltext Search by Property

- JCR.Fulltext Search by All Properties

- Find nt:file document by content of its child jcr:content node

- How to set a new analyzer. Accent symbols ignoring

### 1.31.5. Different analyzers in action

First of all, we will fill repository by nodes with mixin type 'mix:title' and different values of 'jcr:description' property.

- root

  - document1 (mix:title) jcr:description = "The quick brown fox jumped over the lazy dogs"

  - document2 (mix:title) jcr:description = "Brown fox live in forest."

  - document3 (mix:title) jcr:description = "Fox is a nice animal."

Let's see analyzers effect closer. In first case, we use base jcr settings, so, as mentioned above, string "The quick brown fox jumped over the lazy dogs" will be transformed to set {[the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs] }

    // make SQL query

    QueryManager queryManager = workspace.getQueryManager();

    String sqlStatement = "SELECT * FROM mix:title WHERE CONTAINS(jcr:description, 'the')";

    // create query

    Query query = queryManager.createQuery(sqlStatement, Query.SQL);

    // execute query and fetch result

    QueryResult result = query.execute();

NodeIterator will return "document1".

Now change the default analyzer to org.apache.lucene.analysis.StopAnalyzer. Fill repository again (new Analyzer must process nodes properties) and run the same query again. It will return nothing, because stop words like "the" will be excluded from parsed string set.

## 1.32. WebDAV

The WebDAV protocol enables you to use the third party tools to communicate with hierarchical content servers via HTTP. It is possible to add and remove documents or a set of documents from a path on the server. DeltaV is an extension of the WebDav protocol that allows managing document versioning. Locking guarantees protection against multiple access when writing resources. The ordering support allows changing the position of the resource in the list and sort the directory to make the directory tree viewed conveniently. The full-text search makes it easy to find the necessary documents. You can search by using two languages: SQL and XPATH.

In eXo JCR, we plug in the WebDAV layer - based on the code taken from the extension modules of the reference implementation - on the top of our JCR implementation so that it is possible to browse a workspace using the third party tools (it can be Windows folders or Mac ones as well as a Java WebDAV client, such as DAVExplorer or IE using File->Open as a Web Folder).

Now WebDav is an extension of the REST service. To get the WebDav server ready, you must deploy the REST application. Then, you can access any workspaces of your repository by using the following URL:

Standalone mode:

`http://host:port/rest/jcr/{RepositoryName}/{WorkspaceName}/{Path}`

Portal mode:

`http://host:port/portal/rest/private/jcr/{RepositoryName}/{WorkspaceName}/{Path}`

When accessing the WebDAV server with the URL`http://localhost:8080/rest/jcr/repository/production`, you might also use "collaboration" (instead of "production") which is the default workspace in eXo products. You will be asked to enter your login and password. Those will then be checked by using the organization service that can be implemented thanks to an InMemory (dummy) module or a DB module or an LDAP one and the JCR user session will be created with the correct JCR Credentials.

### Note

If you try the "in ECM" option, add "@ecm" to the user's password. Alternatively, you may modify jaas.conf by adding the domain=ecm option as follows:

    exo-domain {
         org.exoplatform.services.security.jaas.BasicLoginModule required domain=ecm;
    };

Related documents

- Link Producer

### 1.32.1. Configuration

    <component>

      <key>org.exoplatform.services.webdav.WebDavServiceImpl</key>

      <type>org.exoplatform.services.webdav.WebDavServiceImpl</type>

      <init-params>

        <!-- this parameter indicates the default login and password values

             used as credentials for accessing the repository -->

        <!-- value-param>

          <name>default-identity</name>

          <value>admin:admin</value>

        </value-param -->

        <!-- this is the value of WWW-Authenticate header -->

        <value-param>

          <name>auth-header</name>

          <value>Basic realm="eXo-Platform Webdav Server 1.6.1"</value>

        </value-param>

        <!-- default node type which is used for the creation of collections -->

        <value-param>

          <name>def-folder-node-type</name>

          <value>nt:folder</value>

        </value-param>

        <!-- default node type which is used for the creation of files -->

        <value-param>

          <name>def-file-node-type</name>

          <value>nt:file</value>

        </value-param>

        <!-- if MimeTypeResolver can't find the required mime type,

             which conforms with the file extension, and the mimeType header is absent

             in the HTTP request header, this parameter is used

             as the default mime type-->

        <value-param>

          <name>def-file-mimetype</name>

          <value>application/octet-stream</value>

        </value-param>

        <!-- This parameter indicates one of the three cases when you update the content of the resource by PUT command.

             In case of "create-version", PUT command creates the new version of the resource if this resource exists.

             In case of "replace" - if the resource exists, PUT command updates the content of the resource and its last modification date.

             In case of "add", the PUT command tries to create the new resource with the same name (if the parent node allows same-name siblings).-->

        <value-param>

          <name>update-policy</name>

          <value>create-version</value>

          <!--value>replace</value -->

          <!-- value>add</value -->

        </value-param>

        <!--

            This parameter determines how service responds to a method that attempts to modify file content.

            In case of "checkout-checkin" value, when a modification request is applied to a checked-in version-controlled resource, the request is automatically preceded by a checkout and followed by a checkin operation.

            In case of "checkout" value, when a modification request is applied to a checked-in version-controlled resource, the request is automatically preceded by a checkout operation.

        -->

        <value-param>

          <name>auto-version</name>

          <value>checkout-checkin</value>

          <!--value>checkout</value -->

        </value-param>

        <!--

            This parameter is responsible for managing Cache-Control header value which will be returned to the client.

            You can use patterns like "text/*", "image/*" or wildcard to define the type of content.

        -->

        <value-param>

          <name>cache-control</name>

          <value>text/xml,text/html:max-age=3600;image/png,image/jpg:max-age=1800;*/*:no-cache;</value>

        </value-param>

        <!--

            This parameter determines the absolute path to the folder icon file, which is shown

            during WebDAV view of the contents

        -->

        <value-param>

          <name>folder-icon-path</name>

          <value>/absolute/path/to/file</value>

        </value-param>

        <!--

            This parameter is responsible for untrusted user agents definition.

            Content-type headers of listed here user agents should be

            ignored and MimeTypeResolver should be explicitly used instead

        -->

        <values-param>

          <name>untrusted-user-agents</name>

          <value>Microsoft Office Core Storage Infrastructure/1.0</value>

        </values-param>

      </init-params>

    </component>

### 1.32.2. Screenshots

At present, eXo JCR WebDav server is tested by using MS Internet Explorer, Dav Explorer, Xythos Drive, Microsoft Office 2003 (as client), and Ubuntu Linux.

#### MS Internet Explorer

(File -> Open as Web Folder)

#### Dav Explorer

#### Xythos Drive

#### Microsoft Office 2003

(as client) (File->Open with typing http://... href in the file name box)

#### Ubuntu Linux

### 1.32.3. Comparison table of WebDav and JCR commands

WebDav | JCR
--- | ---
COPY | Workspace.copy(...)
DELETE | Node.remove()
GET | Node.getProperty(...); Property.getValue()
HEAD | Node.getProperty(...); Property.getLength()
MKCOL | Node.addNode(...)
MOVE | Session.move(...) or Workspace.move(...)
PROPFIND | Session.getNode(...); Node.getNode(...); Node.getNodes(...); Node.getProperties()
PROPPATCH | Node.setProperty(...); Node.getProperty(...).remove()
PUT | Node.addNode("node","nt:file"); Node.setProperty("jcr:data", "data")
CHECKIN | Node.checkin()
CHECKOUT | Node.checkout()
REPORT | Node.getVersionHistory(); VersionHistory.getAllVersions(); Version.getProperties()
RESTORE | Node.restore(...)
UNCHECKOUT | Node.restore(...)
VERSION-CONTROL | Node.addMixin("mix:versionable")
LOCK | Node.lock(...)
UNLOCK | Node.unlock()
ORDERPATCH | Node.orderBefore(...)
SEARCH | Workspace.getQueryManager(); QueryManager.createQuery(); Query.execute()

### 1.32.4. Restrictions

There are some restrictions for WebDAV in different Operating systems.

#### Windows 7

When you try to set up a web folder by “adding a network location” or “map a network drive” through My Computer, you can get an error message saying that either “The folder you entered does not appear to be valid. Please choose another” or “Windows cannot access… Check the spelling of the name. Otherwise, there might be…”. These errors may appear when you are using SSL or non-SSL.

To fix this, do as follows:

1. Go to Windows Registry Editor.

2. Find a key: \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlset\services\WebClient\Parameters\BasicAuthLevel .

3. Change the value to 2.

#### Microsoft Office 2010

If you have Microsoft Office 2010 applications or Microsoft Office 2007 applications installed on a client computer. From that client computer, you try to access an Office file that is stored on a web server that is configured for Basic authentication. The connection between your computer and the web server does not use Secure Sockets Layer (SSL). When you try to open or to download the file, you experience the following symptoms:

- The Office file does not open or download.

- You do not receive a Basic authentication password prompt when you try to open or to download the file.

- You do not receive an error message when you try to open the file. The associated Office application starts. However, the selected file does not open.

To enable Basic authentication on the client computer, follow these steps:

1. Click Start, type regedit in the Start Search box, and then press Enter.

2. Locate and then click the following registry subkey:

   HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\Common\Internet

3. On the Edit menu, point to New, and then click DWORD Value.

4. Type BasicAuthLevel, and then press Enter.

5. Right-click BasicAuthLevel, and then click Modify.

6. In the Value data box, type 2, and then click OK.

## 1.33. FTP

The JCR-FTP Server represents the standard eXo service, operates as an FTP server with an access to a content stored in JCR repositories in the form of nt:file/nt:folder nodes or their successors. The client of an executed Server can be any FTP client. The FTP server is supported by a standard configuration which can be changed as required.

### 1.33.1. Configuration Parameters

#### command-port:

    <value-param>

       <name>command-port</name>

       <value>21</value>

    </value-param>

The value of the command channel port. The value '21' is default.

When you have already some FTP server installed in your system , this parameter needs to be changed (2121 for example) to avoid conflicts or if the port is protected.

#### data-min-port & data-max-port

    <value-param>

       <name>data-min-port</name>

       <value>52000</value>

    </value-param>

    <value-param>

       <name>data-max-port</name>

       <value>53000</value>

    </value-param>

These two parameters indicate the minimal and maximal values of the range of ports, used by the server. The usage of the additional data channel is required by the FTP - protocol, which is used to transfer the contents of files and the listing of catalogues. This range of ports should be free from listening by other server-programs.

#### system

    <value-param>

       <name>system</name>

       <value>Windows_NT</value>

         or

       <value>UNIX Type: L8</value>

    </value-param>

Types of formats of listing of catalogues which are supported.

#### client-side-encoding

    <value-param>

       <name>client-side-encoding</name>

       <value>windows-1251</value>

         or

       <value>KOI8-R</value>

    </value-param>

This parameter specifies the coding which is used for dialogue with the client.

#### def-folder-node-type

    <value-param>

       <name>def-folder-node-type</name>

       <value>nt:folder</value>

    </value-param>

This parameter specifies the type of a node, when an FTP-folder is created.

#### def-file-node-type

    <value-param>

       <name>def-file-node-type</name>

       <value>nt:file</value>

    </value-param>

This parameter specifies the type of a node, when an FTP - file is created.

#### def-file-mime-type

    <value-param>

       <name>def-file-mime-type</name>

       <value>application/zip</value>

    </value-param>

The mime type of a created file is chosen by using its file extention. In case, a server cannot find the corresponding mime type, this value is used.

#### cache-folder-name

    <value-param>

       <name>cache-folder-name</name>

       <value>../temp/ftp_cache</value>

    </value-param>

The Path of the cache folder.

#### upload-speed-limit

    <value-param>

       <name>upload-speed-limit</name>

       <value>20480</value>

    </value-param>

Restriction of the upload speed. It is measured in bytes.

#### download-speed-limit

    <value-param>

       <name>download-speed-limit</name>

       <value>20480</value>

    </value-param>

Restriction of the download speed. It is measured in bytes.

#### timeout

    <value-param>

       <name>timeout</name>

       <value>60</value>

    </value-param>

Defines the value of a timeout.

## 1.34. eXo JCR statistics

This section will show you how to get and manage all statistics provided by eXo JCR.

### 1.34.1. Statistics on the Database Access Layer

In order to have a better idea of the time spent into the database access layer, it can be interesting to get some statistics on that part of the code, knowing that most of the time spent into eXo JCR is mainly the database access. This statistics will then allow you to identify without using any profiler what is normally slow in this layer, which could help to fix the problem quickly.

In case you use `org.exoplatform.services.jcr.impl.storage.jdbc.optimisation.CQJDBCWorkspaceDataContainer` or `org.exoplatform.services.jcr.impl.storage.jdbc.JDBCWorkspaceDataContainer` as `WorkspaceDataContainer`, you can get statistics on the time spent into the database access layer. The database access layer (in eXo JCR) is represented by the methods of the interface `org.exoplatform.services.jcr.storage.WorkspaceStorageConnection`, so for all the methods defined in this interface, we can have the following figures:

- The minimum time spent into the method.

- The maximum time spent into the method.

- The average time spent into the method.

- The total amount of time spent into the method.

- The total amount of time the method has been called.

Those figures are also available globally for all the methods which gives us the global behavior of this layer.

If you want to enable the statistics, you just need to set the JVM parameter called JDBCWorkspaceDataContainer.statistics.enabled to true. The corresponding CSV file is StatisticsJDBCStorageConnection-${creation-timestamp}.csv for more details about how the csv files are managed, please refer to the section dedicated to the statistics manager.

The format of each column header is ${method-alias}-${metric-alias}. The metric alias are described in the statistics manager section.

The name of the category of statistics corresponding to these statistics is JDBCStorageConnection, this name is mostly needed to access to the statistics through JMX.

global | This is the alias for all the methods.
getItemDataById | This is the alias for the method getItemData(String identifier).
getItemDataByNodeDataNQPathEntry | This is the alias for the method getItemData(NodeData parentData, QPathEntry name).
getChildNodesData | This is the alias for the method getChildNodesData(NodeData parent).
getChildNodesCount | This is the alias for the method getChildNodesCount(NodeData parent).
getChildPropertiesData | This is the alias for the method getChildPropertiesData(NodeData parent).
listChildPropertiesData | This is the alias for the method listChildPropertiesData(NodeData parent).
getReferencesData | This is the alias for the method getReferencesData(String nodeIdentifier).
commit | This is the alias for the method commit().
addNodeData | This is the alias for the method add(NodeData data).
addPropertyData | This is the alias for the method add(PropertyData data).
updateNodeData | This is the alias for the method update(NodeData data).
updatePropertyData | This is the alias for the method update(PropertyData data).
deleteNodeData | This is the alias for the method delete(NodeData data).
deletePropertyData | This is the alias for the method delete(PropertyData data).
renameNodeData | This is the alias for the method rename(NodeData data).
rollback | This is the alias for the method rollback().
isOpened | This is the alias for the method isOpened().
close | This is the alias for the method close().

### 1.34.2. Statistics on the JCR API accesses

In order to know exactly how your application uses eXo JCR, it can be interesting to register all the JCR API accesses in order to easily create real life test scenario based on pure JCR calls and also to tune your eXo JCR to better fit your requirements.

In order to allow you to specify the configuration which part of eXo JCR needs to be monitored without applying any changes in your code and/or building anything, we choose to rely on the Load-time Weaving proposed by AspectJ.

To enable this feature, you will have to add in your classpath the following jar files:

- exo.jcr.component.statistics-X.Y.Z.jar corresponding to your eXo JCR version that you can get from the jboss maven repository https://repository.jboss.org/nexus/content/groups/public/org/exoplatform/jcr/exo.jcr.component.statistics.

- aspectjrt-1.6.8.jar that you can get from the main maven repository `http://repo2.maven.org/maven2/org/aspectj/aspectjrt`.

You will also need to get aspectjweaver-1.6.8.jar from the main maven repository http://repo2.maven.org/maven2/org/aspectj/aspectjweaver. At this stage, to enable the statistics on the JCR API accesses, you will need to add the JVM parameter -javaagent:${pathto}/aspectjweaver-1.6.8.jar to your command line, for more details please refer to http://www.eclipse.org/aspectj/doc/released/devguide/ltw-configuration.html.

By default, the configuration will collect statistics on all the methods of the internal interfaces org.exoplatform.services.jcr.core.ExtendedSession and org.exoplatform.services.jcr.core.ExtendedNode, and the JCR API interface javax.jcr.Property. To add and/or remove some interfaces to monitor, you have two configuration files to change that are bundled into the jar exo.jcr.component.statistics-X.Y.Z.jar, which are conf/configuration.xml and META-INF/aop.xml.

The file content below is the content of conf/configuration.xml that you will need to modify to add and/or remove the full qualified name of the interfaces to monitor, into the list of parameter values of the init param called targetInterfaces.

    <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"

     xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd">

     <component>

       <type>org.exoplatform.services.jcr.statistics.JCRAPIAspectConfig</type>

       <init-params>

         <values-param>

           <name>targetInterfaces</name>

           <value>org.exoplatform.services.jcr.core.ExtendedSession</value>

           <value>org.exoplatform.services.jcr.core.ExtendedNode</value>

           <value>javax.jcr.Property</value>

         </values-param>

       </init-params>

      </component>

    </configuration>

The file content below is the content of META-INF/aop.xml that you will need to modify to add and/or remove the full qualified name of the interfaces to monitor, into the expression filter of the pointcut called JCRAPIPointcut. As you can see below, by default only JCR API calls from the exoplatform packages are took into account, don't hesistate to modify this filter to add your own package names.

    <aspectj>

      <aspects>

        <concrete-aspect name="org.exoplatform.services.jcr.statistics.JCRAPIAspectImpl" extends="org.exoplatform.services.jcr.statistics.JCRAPIAspect">

          <pointcut name="JCRAPIPointcut"

            expression="(target(org.exoplatform.services.jcr.core.ExtendedSession) || target(org.exoplatform.services.jcr.core.ExtendedNode) || target(javax.jcr.Property)) &amp;&amp; call(public * *(..))" />

        </concrete-aspect>

      </aspects>

      <weaver options="-XnoInline">

        <include within="org.exoplatform..*" />

      </weaver>

    </aspectj>

The corresponding CSV files are of type Statistics${interface-name}-${creation-timestamp}.csv for more details about how the csv files are managed, please refer to the section dedicated to the statistics manager.

The format of each column header is ${method-alias}-${metric-alias}. The method alias will be of type ${method-name}(list of parameter types separeted by ; to be compatible with the CSV format).

The metric alias are described in the statistics manager section.

The name of the category of statistics corresponding to these statistics is the simple name of the monitored interface (e.g. ExtendedSession for org.exoplatform.services.jcr.core.ExtendedSession), this name is mostly needed to access to the statistics through JMX.

Please note that this feature will affect the performances of eXo JCR so it must be used with caution.

### 1.34.3. Statistics Manager

The statistics manager manages all the statistics provided by eXo JCR, it is responsible of printing the data into the CSV files and also exposing the statistics through JMX and/or Rest.

The statistics manager will create all the CSV files for each category of statistics that it manages, the format of those files is Statistics${category-name}-${creation-timestamp}.csv. Those files will be created into the user directory if it is possible otherwise it will create them into the temporary directory. The format of those files is `CSV` (i.e. Comma-Seperated Values), one new line will be added regularily (every 5 seconds by default) and one last line will be added at JVM exit. Each line, will be composed of the 5 figures described below for each method and globaly for all the methods.

Min | The minimum time spent into the method expressed in milliseconds.
Max | The maximum time spent into the method expressed in milliseconds.
Total | The total amount of time spent into the method expressed in milliseconds.
Avg | The average time spent into the method expressed in milliseconds.
Times | The total amount of times the method has been called.

You can disable the persistence of the statistics by setting the JVM parameter called JCRStatisticsManager.persistence.enabled to false, by default, it is set to true. You can aslo define the period of time between each record (i.e. line of data into the file) by setting the JVM parameter called JCRStatisticsManager.persistence.timeout to your expected value expressed in milliseconds, by default it is set to 5000.

You can also access to the statistics thanks to JMX, the available methods are the following:

getMin | Give the minimum time spent into the method corresponding to the given category name and statistics name. The expected arguments are the name of the category of statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
getMax | Give the maximum time spent into the method corresponding to the given category name and statistics name. The expected arguments are the name of the category of statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
getTotal | Give the total amount of time spent into the method corresponding to the given category name and statistics name. The expected arguments are the name of the category of statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
getAvg | Give the average time spent into the method corresponding to the given category name and statistics name. The expected arguments are the name of the category of statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
getTimes | Give the total amount of times the method has been called corresponding to the given ,category name and statistics name. The expected arguments are the name of the category of statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
reset | Reset the statistics for the given category name and statistics name. The expected arguments are the name of the category of statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
resetAll | Reset all the statistics for the given category name. The expected argument is the name of the category of statistics (e.g. JDBCStorageConnection).

The full name of the related MBean is exo:service=statistic, view=jcr.

## 1.35. Checking repository integrity and consistency

Production and any other system may have faults some day. They may be caused by hardware problems, human faults, software errors during updates and many other circumstances. It is important to check integrity and consistency of the system if there is no backup or it is stale, or recovery process takes much. Exo JCR implementation offers an innovative JMX-based complex checking tool. Running inspection, tool check every major JCR component, such as persistent data layer and index. Persistent layer includes JDBC Data Container and Value-Storages if they are configured. Database verified using the set of complex specialized domain-specific queries. Value Storage tool checks existence and access to each file. Index verification contains two-way pass cycle, existence of each node in index checks on persistent layer along with opposite direction, when each node from Data Container validated in index. Access to check tool is exposed via JMX interface, with the following operation available:

checkAll() | Inspect full repository data (db, value storage and search index)
checkDataBase() | Inspect only DB
checkValueStorage() | Inspect only ValueStorage
checkIndex() | Inspect only SearchIndex

Also tools provides abilities to repair possible inconsistencies:

- Item has no parent node - properties will be removed and root UUID will be assigned in case of nodes

- Node has a single valued property with nothing declared in the VALUE table - property will be removed if it is not required by primary type of its node

- Node has no primary type property - node and whole subtree will be removed if node is not required by primary type of its parent

- Value record has no related property record - value record will be removed from database

- Item is its own parent - properties will be removed and root UUID will be assigned in case of nodes

- Several versions of same item - all earler records with earler versions will be removed from ITEM table

- Reference properties without reference records - property will be removed if it is not required by primary type of its node

- Node is marked as locked in the lockmanager's table but is not in the ITEM table or the opposite - all lock inconsistencies will be removed from both tables

- Property's value is stored in the File System but content is missing - new empty file corresponding to value will be created

repairDataBase() | repair DB
repairValueStorage() | repair ValueStorage

All tool activities are stored in file, which can be found in app directory by name `report-<repository name>-dd-MMM-yy-HH-mm.txt`.

### 1.35.1. Recommendations on how to fix corrupted JCR

Please, before applying the next recommendations, don't forget to backup your data first. To get the JCR back to normal mode you may require to remove potentially valuable data. It is also recommended to keep the results of queries that check data consistency. This may be needed for the support team in case of deeper restoration process.

Here are listed the examples of corruptions and ways to eliminate them:

### Note

It is assumed that queries for single and multi db configurations differs only by JCR_xITEM table name, otherwise queries will be explicitly introduced.

### Note

In some examples you'll be asked to replace some identificator with corresponding value. That basically means that you need to insert values, from each row result of query executed during issue detection stage, to corresponding place. Explicit explanation of what to do will be introduced in case replacing is needed to be fulfilled in other way.

1. Item has no parent node

   - To detect an issue you need to execute the following query:

         select * from JCR_SITEM I where NOT EXISTS(select * from JCR_SITEM P where P.ID = I.PARENT_ID)

   - Fix description:

     Assign root as parent node to be able to delete later if node is not needed anymore

   - To fix do the following:

     - For all query results rows containing items belonging to I_CLASS = 1 (nodes)

       execute next query replacing ${ID} and ${CONTAINER_NAME} by corresponding values:

       - Single DB

             update JCR_SITEM set PARENT_ID='${CONTAINER_NAME}00exo0jcr0root0uuid0000000000000' where ID = '${ID}'

       - Multi DB

             update JCR_MITEM set PARENT_ID='00exo0jcr0root0uuid0000000000000' where ID = '${ID}'

     - For all query results rows containing items belonging to I_CLASS = 2 (property)

           delete from JCR_SREF where PROPERTY_ID = '${ID}'
           delete from JCR_SVALUE where PROPERTY_ID = '${ID}'
           delete from JCR_SITEM where PARENT_ID = '${ID}' or ID='${ID}'

2. Node has a single valued properties with no declaration in VALUE table

   - To detect an issue you need to execute the following query:

         select * from JCR_SITEM P where P.I_CLASS=2 and P.P_MULTIVALUED=0 and NOT EXISTS (select * from JCR_SVALUE V where V.PROPERTY_ID=P.ID)

     ### Note

     P_MULTIVALUED=0 should be replacted by P_MULTIVALUED='f' for PostgreSQL

   - Fix description:

     Simply remove corrupted properties

   - To fix for every row execute next queries replacing ${ID} by corresponding value:

         delete from JCR_SREF where PROPERTY_ID = '${ID}'
         delete from JCR_SITEM where ID = '${ID}'

3. Node has no primary type property

   - To detect an issue you need to execute the following query:

         select * from JCR_SITEM N where N.I_CLASS=1 and NOT EXISTS (select * from JCR_SITEM P where P.I_CLASS=2 and P.PARENT_ID=N.ID and P.NAME='[http://www.jcp.org/jcr/1.0]primaryType')

   - Fix description:

     Remove node, all its children, properties, values and reference records

   - To fix do the following:

     - First step is to recursivly travers to the bottom of the tree:

           select * from JCR_SITEM where PARENT_ID='${ID}' and I_CLASS=1

       until query results in empty value. You'll receive a tree structure containing a node, its children and properties.

     - Second step is to execute the following steps with tree structure elements in reverse order (from leaves to head)

       Execute query for tree element's ${ID}

           select * from JCR_SITEM where PARENT_ID='${ID}'

       Execute queries for each ${ID} received during mentioned above query execution

           delete from JCR_SREF where PROPERTY_ID = '${ID}'
           delete from JCR_SVALUE where PROPERTY_ID = '${ID}'
           delete from JCR_SITEM where PARENT_ID = '${ID}' or ID='${ID}'

4. All value records have no related property record

   - To detect an issue you need to execute the following query:

         select * from JCR_SVALUE V where NOT EXISTS(select * from JCR_SITEM P where V.PROPERTY_ID = P.ID and P.I_CLASS=2)

   - Fix description:

     Remove these unnecessary records from JCR_SVALUE table

   - To fix do the following:

     For every row execute next queries replacing ${ID} with corresponding value:

         delete from JCR_SVALUE where ID = '${ID}'

5. Corrupted VALUE records. Both STORAGE_DESC and DATA fields contain not null value

   - To detect an issue you need to execute the following query:

         select * from JCR_SVALUE where (STORAGE_DESC is not null and DATA is not null)

   - Fix description:

     Set null for STORAGE_DESC field

   - To fix do the following:

     For every row execute next queries replacing ${ID} with corresponding value:

         update JCR_SVALUE set STORAGE_DESC = null where ID = '${ID}'

   ### Note

   For Sybase DB "DATA is not null" must be replaced by "not DATA like null"

6. Item is its own parent

   - To detect an issue you need to execute the following query:

         select * from JCR_SITEM I where I.ID = I.PARENT_ID and I.NAME <> '__root_parent'

   - Fix description:

     Assign root as parent node to be able to delete later if node is not needed to use anymore

   - To fix do the following:

     - For all query results rows containing items belonging to I_CLASS = 1 (nodes)

       execute next query replacing ${ID} and ${CONTAINER_NAME} with corresponding values:

       - Single DB

             update JCR_SITEM set PARENT_ID='${CONTAINER_NAME}00exo0jcr0root0uuid0000000000000' where ID = '${ID}'

       - Multi DB

             update JCR_MITEM set PARENT_ID='00exo0jcr0root0uuid0000000000000' where ID = '${ID}'

     - For all query results rows containing items belonging to I_CLASS = 2 (property)

           delete from JCR_SREF where PROPERTY_ID = '${ID}'
           delete from JCR_SVALUE where PROPERTY_ID = '${ID}'
           delete from JCR_SITEM where PARENT_ID = '${ID}' or ID='${ID}'

7. Several versions of same item

   - To detect an issue you need to execute the following query:

         select * from JCR_SITEM I where EXISTS (select * from JCR_SITEM J WHERE I.CONTAINER_NAME = J.CONTAINER_NAME and I.PARENT_ID = J.PARENT_ID AND I.NAME = J.NAME and I.I_INDEX = J.I_INDEX and I.I_CLASS = J.I_CLASS and I.VERSION != J.VERSION)

   - Fix description:

     Keep the newest version and remove the others

   - To fix do the following:

     - Grouping

           select max(VERSION) as MAX_VERSION, PARENT_ID, NAME, CONTAINER_NAME, I_CLASS, I_INDEX  from JCR_SITEM WHERE I_CLASS=2 GROUP BY PARENT_ID, CONTAINER_NAME, NAME, I_CLASS, I_INDEX HAVING count(VERSION) > 1

     - Execute the following query, replacing ${PARENT_ID} and ${CONTAINER_NAME}, ${NAME}, ${I_CLASS}, ${I_INDEX}, ${MAX_VERSION} with corresponding values contained in results of mentioned above query:

       - Single DB

             select * from JCR_SITEM where I.CONTAINER_NAME='${CONTAINER_NAME}' and PARENT_ID='${PARENT_ID}' and NAME='${NAME}' and I_CLASS='${I_CLASS}' and I_INDEX='${I_INDEX}' and VERSION < ${MAX_VERSION}

       - Multi DB

             select * from JCR_SITEM where PARENT_ID='${PARENT_ID}' and NAME='${NAME}' and I_CLASS='${I_CLASS}' and I_INDEX='${I_INDEX}' and VERSION < ${MAX_VERSION}

       Execute the following queries, replacing ${ID} with corresponding values of newly obtained results.

           delete from JCR_SREF where PROPERTY_ID = '${ID}'
           delete from JCR_SVALUE where PROPERTY_ID = '${ID}'
           delete from JCR_SITEM where ID='${ID}'

8. Reference properties without reference records

   - To detect an issue you need to execute the following query:

         select * from JCR_SITEM P, JCR_SVALUE V where P.ID = V.PROPERTY_ID and P.P_TYPE=9 and NOT EXISTS (select * from JCR_SREF R where P.ID=R.PROPERTY_ID)

   - Fix description:

     Remove broken reference properties

   - To fix do the following:

     Execute the query replacing ${ID} with corresponding value

         delete from JCR_SVALUE where PROPERTY_ID = '${ID}'
         delete from JCR_SITEM where ID = '${ID}'

9. Node, considered to be locked in the lockmanager data, is not locked according to the jcr data or the opposite situation

   - To detect an issue you need:

     First get all locked nodes IDs in repository, mentioned in JCR_xITEM table, by executing a query:

         select distinct PARENT_ID from JCR_SITEM where I_CLASS=2 and (NAME='[http://www.jcp.org/jcr/1.0]lockOwner' or NAME='[http://www.jcp.org/jcr/1.0]lockIsDeep')

     Then compare it to nodes IDs from LockManager's table

     - JBC

       ### Note

       During comparing results be aware that for single DB configurations you need to cut off ID prefix representing workspace name for results obtained from JCR_xITEM table.

       ### Note

       Though usually single lock table is used for the whole repository, it is possible to configure separate db lock tables for each workspace, in this case to obtain information over repository you need to execute queries for each table.

       - Non shareable

             select fqn from ${LOCK_TABLE} where parent='/$LOCKS'

       - Shareable

         Replace ${REPOSITORY_NAME} with corresponding value

             select fqn from ${LOCK_TABLE} where parent like '/${REPOSITORY_NAME}%/$LOCKS/'

     - ISPN

       ### Note

       For ISPN lock tables are defined for each workspace separately, so to obtain information over repository you must execute queries for all lock tables.

       To get all set of locked node IDs in repository you must execute the following query for each workspace

           select id from ${LOCK_TABLE}

   - Fix description:

     Remove inconsistent lock entries and properties. Remove entries in LOCK_TABLE that have no corresponding properties in JCR_xITEM table and remove JCR_xITEM properties that have no corresponding entries in LOCK_TABLE.

   - To fix do the following:

     First remove property values, replace ${ID} with corresponding node ID

         delete from JCR_SVALUE where PROPERTY_ID in (select ID from JCR_SITEM where PARENT_ID='${ID}' and (NAME = '[http://www.jcp.org/jcr/1.0]lockIsDeep' or NAME = '[http://www.jcp.org/jcr/1.0]lockOwner'))

     Than remove property items themselves, replace ${ID} with corresponding node ID

         delete from JCR_SITEM where PARENT_ID='${ID}' and (NAME = '[http://www.jcp.org/jcr/1.0]lockIsDeep' or NAME = '[http://www.jcp.org/jcr/1.0]lockOwner')

     Replace ${ID} and ${FQN} with corresponding node ID and FQN

     - JBC

           delete from ${LOCK_TABLE} where fqn = '${FQN}'

     - ISPN

       Execute the following query for each workspace

           delete from ${LOCK_TABLE} where id = '${ID}'

10. A property's value is stored in the file system, but its content is missing

    This cannot be checked via simple SQL queries

## 1.36. JTA

eXo JCR supports the Java Transaction API out of the box. If a TransactionService has been defined (refer to the section about the TransactionService for more details) at session save, it checks if a global transaction is active and if so, it automatically enrolles the JCR session in the global transaction. If you intend to use a managed data source, you will have to configure the service DataSourceProvider (for more details please refer to the corresponding section).

## 1.37. The JCA Resource Adapter

eXo JCR supports J2EE Connector Architecture 1.5, thus If you would like to delegate the JCR Session lifecycle to your application server, you can use the JCA Resource Adapter for eXo JCR if your application server supports JCA 1.5. This adapter only supports XA Transaction, in other words you cannot use it for local transactions. Since the JCR Sessions have not been designed to be shareable, the session pooling is simply not covered by the adapter.

### 1.37.1. The SessionFactory

The equivalent of the javax.resource.cci.ConnectionFactory in JCA terminology is org.exoplatform.connectors.jcr.adapter.SessionFactory in the context of eXo JCR, the resource that you will get thanks to a JNDI lookup is of type SessionFactory and provides the following methods:

       /**
        * Get a JCR session corresponding to the repository
        * defined in the configuration and the default workspace.
        * @return a JCR session corresponding to the criteria
        * @throws RepositoryException if the session could not be created
        */
       Session getSession() throws RepositoryException;

       /**
        * Get a JCR session corresponding to the repository
        * defined in the configuration and the default workspace, using
        * the given user name and password.
        * @param userName the user name to use for the authentication
        * @param password the password to use for the authentication
        * @return a JCR session corresponding to the criteria
        * @throws RepositoryException if the session could not be created
        */
       Session getSession(String userName, String password) throws RepositoryException;

       /**
        * Get a JCR session corresponding to the repository
        * defined in the configuration and the given workspace.
        * @param workspace the name of the expected workspace
        * @return a JCR session corresponding to the criteria
        * @throws RepositoryException if the session could not be created
        */
       Session getSession(String workspace) throws RepositoryException;

       /**
        * Get a JCR session corresponding to the repository
        * defined in the configuration and the given workspace, using
        * the given user name and password.
        * @param workspace the name of the expected workspace
        * @param userName the user name to use for the authentication
        * @param password the password to use for the authentication
        * @return a JCR session corresponding to the criteria
        * @throws RepositoryException if the session could not be created
        */
       Session getSession(String workspace, String userName, String password) throws RepositoryException;

### 1.37.2. Configuration

PortalContainer | In case of the portal mode, if no portal container can be found in the context of the request, the adapter will use the value of this parameter to get the name of the expected portal container to create the JCR sessions. In case of a standalone mode, this parameter is not used. This parameter is optional, by default the default portal container will be used.
Repository | The repository name used to create JCR sessions. This parameter is optional, by default the current repository will be used.

### 1.37.3. Deployment

In case of the standalone mode where the JCR and its dependencies are not provided, you will need to deploy the whole ear file corresponding to the artifactId exo.jcr.ear and groupId org.exoplatform.jcr, the rar file is embedded into the ear file. In case the JCR and its dependencies are provided like when you use it with gateIn for example, you will need to deploy only the rar file corresponding to the artifactId exo.jcr.connectors.jca and groupId org.exoplatform.jcr.

Then you will need to configure the connector itself, for example for JBoss AS, you need to create in your deploy directory a file of type *-ds.xml (jcr-ds.xml for example) with the following content:

    <connection-factories>
      <tx-connection-factory>
        <jndi-name>jcr/repository</jndi-name>
        <xa-transaction/>
        <!-- The rar name will be exo.jcr.connectors.jca.X.Y.Z.rar in case you deploy only the rar file -->
        <rar-name>exo.jcr.ear.ear#exo-jcr.rar</rar-name>
        <adapter-display-name>eXo JCR Adapter</adapter-display-name>
        <connection-definition>org.exoplatform.connectors.jcr.adapter.SessionFactory</connection-definition>
        <!--
        <config-property name="PortalContainer" type="java.lang.String">portal</config-property>
        -->
        <config-property name="Repository" type="java.lang.String">repository</config-property>
      </tx-connection-factory>
    </connection-factories>

## 1.38. Access Control

eXo JCR is a complete implementation of the standard JSR 170: Content Repository for Java TM Technology API, including Level 1, Level 2 and Additional Features specified in the JCR Specification.

### 1.38.1. Standard Action Permissions

The JCR specification (JSR 170) does not have many requirements about Access Control. It only requires the implementation of the Session.checkPermission(String absPath, String actions) method. This method checks if a current session has permissions to perform some actions on absPath:

- absPath : The string representation of a JCR absolute path.

- actions : eXo JCR interprets this string as a comma separated the list of individual action names, such as the 4 types defined in JSR 170 :

  - add_node : Permission to add a node.

  - set_property : Permission to set a property.

  - remove : Permission to remove an item (node or property).

  - read : Permission to retrieve a node or read a property value.

For example :

- session.checkPermission("/Groups/organization", "add_node,set_property") will check if the session is allowed to add a child node to "organization" and to modify its properties. If one of the two permissions is denied, an AccessDeniedException is thrown.

- session.checkPermission("/Groups/organization/exo:name", "read,set_property") will check if the session is allowed to read and change the "exo:name" property of the "organization" node.

- session.checkPermission("/Groups/organization/exo:name", "remove") will check if the session allowed to remove "exo:name" property or node.

### 1.38.2. eXo Access Control

The JSR170 specification does not define how permissions are managed or checked. So eXo JCR has implemented its own proprietary extension to manage and check permissions on nodes. In essence, this extension uses an Access Control List (ACL) policy model applied to eXo Organization model (see eXo Platform Organization Service).

#### Principal and Identity

At the heart of eXo Access Control, is the notion of the identity concept. Access to JCR is made through sessions acquired against a repository. Sessions can be authenticated through the standard (but optional) repository login mechanism. Each session is associated with a principal. The principal is an authenticated user or group that may act on JCR data. The identity is a string identifying this group or user.'

There are 3 reserved identities that have special meanings in eXo JCR:

- any : represents any authenticated session.

- anonim : represents a principal for non authenticated sessions. (No error, it's really "\_\_anonim".)

- system : represents a principal for system sessions, typically used for administrative purposes. System session has full access (all permissions) to all nodes; therefore be careful when working with system sessions.

#### ACL

An access control list (ACL) is a list of permissions attached to an object. An ACL specifies which users, groups or system processes are granted access to JCR nodes, as well as what operations are allowed to be performed on given objects.

eXo JCR Access Control is based on two facets applied to nodes :

- Privilegeable : Means that the user or group (also called principal) needs the appropriate privileges to access to this node. The privileges are defined as (positive) permissions that are granted to users or groups.

- Ownable : The node has an owner. The owner has always full access (all permissions) to the node, independent of the privilegeable facet.

##### Privilegeable

A privilegeable node defines the permissions required for actions on this node. For this purpose, it contains an ACL.

At JCR level, this is implemented by an exo:privilegeable mixin.

    <nodeType name="exo:privilegeable" isMixin="true" hasOrderableChildNodes="false" primaryItemName="">

       <propertyDefinitions>

          <propertyDefinition name="exo:permissions" requiredType="Permission" autoCreated="true" mandatory="true"

                              onParentVersion="COPY" protected="true" multiple="true">

             <valueConstraints/>

          </propertyDefinition>

       </propertyDefinitions>

    </nodeType>

A privilegeable node can have multiple exo:permissions values. The type of these values is the eXo JCR specific Permission type. The Permission type contains a list of ACL.

The possible values are corresponding to JCR standard actions:

- read: The node or its properties can be read.

- remove: The node or its properties can be removed.

- add_node : Child nodes can be added to this node.

- set_property : The node's properties can be modified, added or removed.

##### Ownable

An ownable node defines an owner identity. The owner has always full privileges. These privileges are independent of the permissions set by exo:permissions. At JCR level, the ownership is implemented by an exo:owneable mixin. This mixin holds an owner property.

    <nodeType name="exo:owneable" isMixin="true" hasOrderableChildNodes="false" primaryItemName="">

       <propertyDefinitions>

          <propertyDefinition name="exo:owner" requiredType="String" autoCreated="true" mandatory="true" onParentVersion="COPY"

                              protected="true" multiple="false">

             <valueConstraints/>

          </propertyDefinition>

       </propertyDefinitions>

    </nodeType>

The exo:owner property value contains exactly one identity string value. There might be a long list of different permissions for different identities (user or groups). All permissions are always positive permissions; denials are not possible. When checking a permission of an action, it's therefore perfectly sufficient that the principal of a session belongs to the groups to which the concerned action is granted.

##### ACL Inheritance

To grant or deny access to a node, eXo JCR applies a privilege resolving logic at node access time.

If a node is privilegeable, the node's ACL is used exclusively. If the ACL does not match the principal's identity, the principal has no access (except the owner of the node).

Non-privilegeable nodes inherit permissions from their parent node. If the parent node is not privilegeable either, the resolving logic looks further up the node hierarchy and stops with the first privilegeable ancestor of the current node. All nodes potentially inherit from the workspace root node.

The owner of a node is inherited in accordance with the same logic: If the node has no owner, the owner information of the closest owneable ancestor is inherited.

This inheritance is implemented by browsing up the node's hierarchy. At access time, if the node does not have owner or permissions, the system looks up into the node's ancestor hierarchy for the first ACL.

##### Default ACL of the root node

When no matching ACL is found in the ancestor hierarchy, the system may end up looking at the root node's ACL. As ACL are optional, even for the root node, if the root node has no ACL, the following rule is ultimately applied to resolve privileges:

- any identity (any authenticated session) is granted all permissions

#### Notes

Access Control nodetypes are not extendible: The access control mechanism works for exo:owneable and exo:privilegeable nodetypes only, not for their subtypes! So you cannot extend those nodetypes.

Autocreation: By default, newly created nodes are neither exo:privilegeable nor exo:owneable but it is possible to configure the repository to auto-create exo:privilegeable or/and exo:owneable thanks to eXo's JCR interceptors extension (see JCR Extensions)

OR-based Privilege Inheritance: Note, that eXo's Access Control implementation supports a privilege inheritance that follows a strategy of either...or/ and has only an ALLOW privilege mechanism (there is no DENY feature). This means that a session is allowed to perform some operations on some nodes if its identity has an appropriate permission assigned to this node. Only if there is no exo:permission property assigned to the node itself, the permissions of the node's ancestors are used.

#### Example

##### XML Example

In the following example, you see a node named "Politics" which contains two nodes named "Cats" and "Dogs".

### Note

These examples are exported from eXo DMS using the \"document view\" representation of JCR. Each value of a multi-value property is separated by a whitespace, each whitespace is escaped by x0020.

    <Politics  jcr:primaryType="nt:unstructured" jcr:mixinTypes="exo:owneable exo:datetime exo:privilegeable" exo:dateCreated="2009-10-08T18:02:43.687+02:00"

    exo:dateModified="2009-10-08T18:02:43.703+02:00"

    exo:owner="root"

    exo:permissions="any_x0020_read *:/platform/administrators_x0020_read *:/platform/administrators_x0020_add_node *:/platform/administrators_x0020_set_property *:/platform/administrators_x0020_remove">

    <Cats jcr:primaryType="exo:article"

    jcr:mixinTypes="exo:owneable"

    exo:owner="marry"

    exo:summary="The_x0020_secret_x0020_power_x0020_of_x0020_cats_x0020_influences_x0020_the_x0020_leaders_x0020_of_x0020_the_x0020_world."

    exo:text="" exo:title="Cats_x0020_rule_x0020_the_x0020_world" />

    <Dogs jcr:primaryType="exo:article"

    jcr:mixinTypes="exo:privilegeable"

    exo:permissions="manager:/organization_x0020_read manager:/organization_x0020_set_property"

    exo:summary="Dogs"

    exo:text="" exo:title="Dogs_x0020_are_x0020_friends" />

    </Politics>

The "Politics" node is exo:owneable and exo:privilegeable. It has both an exo:owner property and an exo:permissions property. There is an exo:owner="root" property so that the user root is the owner. In the exo:permissions value, you can see the ACL that is a list of access controls. In this example, the group *:/platform/administrators has all rights on this node (remember that the "*" means any kind of membership). any means that any users also have the read permission.s

As you see in the jcr:mixinTypes property, the "Cats" node is exo:owneable and there is an exo:owner="marry" property so that the user marry is the owner. The "Cats" node is not exo:privilegeable and has no exo:permissions. In this case, we can see the inheritance mechanism here is that the "Cats" node has the same permissions as "Politics" node.

Finally, the "Dogs" node is also a child node of "Politics". This node is not exo:owneable and inherits the owner of the "Politics" node (which is the user root). Otherwise, "Dogs" is exo:privilegeable and therefore, it has its own exo:permissions. That means only the users having a "manager" role in the group "/organization" and the user "root" have the rights to access this node.

##### Inheritance Examples

Here is an example showing the accessibility of two nodes (to show inheritance) for two sample users named manager and user:

The "+" symbol means that there is a child node "exo:owneable".

##### Permission validation

This session describes how permission is validated for different JCR actions.

- read node: Check the read permission on a target node.

  For example: Read /node1/subnode node, JCR will check the "read" permission exactly on "subnode".

- read property : Check the read permission on a parent node.

  For example: Read /node1/myprop - JCR will check the "read" permission on "node1".

- add node: Check add_node on a parent node.

  For example: Add /node1/subnode node, JCR will check the "add_node" permission on "node1".

- set property: set_property on a parent node.

  For example: Try to set /node1/myprop property, JCR will check the "set_property" permission on "node1".

- remove node: Check the remove permission on a target node.

  For example: Try to remove /node1/subnode node, JCR will check the "remove" permission on "subnode".

- remove property: Check the remove permission on a parent node.

  For example: Try to remove /node1/myprop property, JCR will check the "remove" permission on "node1".

- add mixin: Check the "add_node" and "set_property" permission on a target node.

  For example: Try to add mixin to /node1/subnode node, JCR will check the "add_node" and "set_property" permission on "subnode".

### Note

The behavior of the permission "remove" and "add mixin" validation has changed since JCR 1.12.6-GA. The old behavior is:

- remove node: Check the remove permission on a parent node.

  For example: Try to remove /node1/subnode node, JCR will check the "remove" permission on "node1".

- add mixin: Check the "add_node" and "set_property" permission on a parent node.

  For example: Try to add mixin to /node1/subnode node, JCR will check the "add_node" and "set_property" permission on "node1".

#### Java API

eXo JCR's ExtendedNode interface which extends javax.jcr.Node interface provides additional methods for Access Control management.

Method signature | Description
--- | ---
void setPermissions(Map<String, String[]> permissions) | Assigns a set of Permissions to a node
void setPermission(String identity, String[] permission) | Assigns some Identities' Permission to a node
void removePermission(String identity) | Removes an Identity's Permission
void removePermission(String identity, String permission) | Removes the specified permission for a particular identity
void clearACL() | Clears the current ACL so it becomes default
AccessControlList getACL() | Returns the current ACL
void checkPermission(String actions) | Checks Permission (AccessDeniedException will be thrown if denied)

The "identity" parameter is a user or a group name. The permissions are the literal strings of the standard action permissions (add_node, set_property, remove, read).

## 1.39. Access Control Extension

An extended Access Control system consists of:

- Specifically configured custom ExtendedAccessManager which is called by eXo JCR internals to check if user's Session (user) has some privilege to perform some operation or not.

- The Action sets a thread local InvocationContext at runtime, the InvocationContext instance is then used by the ExtendedAccessManager in handling permissions of the current Session.

- InvocationContext is a collection of properties which reflect the state of a current Session. At present, it contains: the type of the current operation on Session (event), current Item (javax.jcr.Item) on which this operation is performed and the current eXo Container

### 1.39.1. Prerequisites

This is an extension of eXo JCR Access Control features. Please read Access Control and JCR Extensions topics first.

### 1.39.2. Access Context Action

SetAccessControlContextAction implements Action and may be called by SessionActionInterceptor as a reaction of some events - usually before writing methods and after reading (getNode(), getProperty() etc). This SetAccessControlContextAction calls the AccessManager.setContext(InvocationContext context) method which sets the ThreadLocal invocation context for the current call.

Action's Configuration may look like as the following:

    <value>

      <object type="org.exoplatform.services.jcr.impl.ext.action.ActionConfiguration">

        <field  name="eventTypes"><string>addNode,read</string></field>

        <field  name="workspace"><string>production</string></field >

        <field  name="actionClassName"><string>org.exoplatform.services.jcr.ext.access.SetAccessControlContextAction</string></field>

      </object>

    </value>

### 1.39.3. The Invocation Context

The InvocationContext contains the current Item, the current ExoContainer and the current EventType is like below:

    public interface InvocationContext extends ContextBase {

      Item getCurrentItem();

      int getEventType();

      ExoContainer getContainer();

    }

### 1.39.4. Custom Extended Access Manager

By default, all Workspaces share an AccessManager instance, created by RepositoryService at the startup (DefaultAccessManagerImpl) which supports default access control policy as described in the Access Control section. Custom Access Control policy can be applied to certain Workspace configuring access-manager element inside workspace as follows:

    <workspace name="ws">

       ...

       <!-- after query-handler element -->

       <access-manager class="org.exoplatform.services.jcr.CustomAccessManagerImpl">

          <properties>

             <property name="someProperty" value="value"/>

             ...

          </properties>

      </access-manager>

      ...

    </workspace>

When implementing AccessManager, hasPermission() method has to be overriden so it uses the current invocation context at its discretion. For instance, it may get the current node's metadata and make a decision if the current User has appropriate permissions. Use Invocation Context's runtime properties to make a decision about current Session's privileges (see the Example below)

Simplified Sequence diagram for the Session.getNode() method (as an Example):

### 1.39.5. Example of a custom Access Manager

The sample CustomAccessManagerImpl below extends the default access manager and uses some DecisionMakingService in the overloaded hasPermission method to find out if a current user has permission to use current item, event type, user and some parameter of AccessManager. To make this Access manager work, it is necessary to configure it in jcr configuration as mentioned in Custom Extended Access Manager and SetAccessControlContextAction should be configured in the way mentioned in Access Context Action.

    public class CustomAccessManagerImpl extends AccessManager {

      private String property;

      private DecisionMakingService theService;

      public CustomAccessManagerImpl (RepositoryEntry config, WorkspaceEntry wsConfig,

          DecisionMakingService someService) throws RepositoryException, RepositoryConfigurationException {

        super(config, wsConfig);

        this.property = wsConfig.getAccessManager().getParameterValue("someParam");

        this.theService = someService;

      }

      @Override

      public boolean hasPermission(AccessControlList acl, String[] permission, Identity user) {

        // call the default permission check

        if (super.hasPermission(acl, permission, user)) {

          Item curItem = context().getCurrentItem();

          int eventType = context().getEventType();

          ExoContainer container = context().getContainer();

          // call some service's method

          return theService.makeDecision(curItem, eventType, user, property);

        } else {

          return false;

        }

      }

    }

## 1.40. Link Producer Service

Link Producer Service - a simple service, which generates an .lnk file, that is compatible with the Microsoft link file format. It is an extension of the REST Framework library and is included into the WebDav service. On dispatching a GET request the service generates the content of an .lnk file, which points to a JCR resource via WebDav.

Link Producer has a simple configuration like described below:

    <component>

      <key>org.exoplatform.services.webdav.lnkproducer.LnkProducer</key>

      <type>org.exoplatform.services.webdav.lnkproducer.LnkProducer</type>

    </component>

When using JCR the resource can be addressed by WebDav reference (href) like `http://host:port/rest/jcr/repository/workspace/somenode/somefile.extention` , the link servlet must be called for this resource by several hrefs, like `http://localhost:8080/rest/lnkproducer/openit.lnk?path=/repository/workspace/somenode/somefile.extention`

Please note, that when using the portal mode the REST servlet is available using a reference (href) like `http://localhost:8080/portal/rest/...`

The name of the .lnk file can be any. But for the best compatibility it must be the same as the name of the JCR resource.

Here is a step by step sample of a use case of the link producer... At first, type valid reference to the resource, using the link producer in your browser's adress field:

Internet Explorer will give a dialog window requesting to Open a file or to Save it. Click on the Open button

In Windows system an .lnk file will be downloaded and opened with the application which is registered to open the files, which are pointed to by the .lnk file. In case of a .doc file, Windows opens Microsoft Office Word which will try to open a remote file (test0000.doc). Maybe it will be necessary to enter USERNAME and PASSWORD.

Next, you will be able to edit the file in Microsoft Word.

The Link Producer is necessary for opening/editing and then saving the remote files in Microsoft Office Word, without any further updates.

Also the Link Producer can be referenced to from an HTML page. If page contains code like

    <a href="http://localhost:8080/rest/lnkproducer/openit.lnk?path=/repository/workspace/somenode/somefile.extention">somefile.extention</a>

the file "somefile.extention" will open directly.

## 1.41. Binary Values Processing

Processing binary large object (BLOB) is very important in eXo JCR, so this section focuses on explaining how to do it.

### 1.41.1. Configuration

Binary large object (BLOB) properties can be stored in two ways in the eXo JCR: in the database with items information or in an external storage on host file system. These options can be configured at workspace in the repository configuration file (repository-configuration.xml in portal and exo-jcr-config.xml in standalone mode). The database storage can't be completely disabled.

The first case is optimal for most of cases which you do not use very large values or/and do not have too many BLOBs. The configuration of the BLOBs size and BLOBs quantity in a repository depend on your database features and hardware.

The second case is to use an external values storage. The storage can be located on a built-in hard disk or on an attached storage. But in any cases, you should access to the storage as if it was a regular file(s). The external value storage is optional and can be enabled in a database configuration.

### Note

eXo JCR Repository service configuration basics is discussed in Configuration

Database and workspace persistence storage configuration is discussed in JDBC Data Container config

Configuration details for External Value Storages.

### 1.41.2. Usage

In both of the cases, a developer can set/update the binary Property via Node.setProperty(String, InputStream), Property.setValue(InputStream) as described in the spec JSR-170. Also, there is the setter with a ready Value object (obtainer from ValueFactory.createValue(InputStream)).

An example of a specification usage.

    // Set the property value with given stream content.

    Property binProp = node.setProperty("BinData", myDataStream);

    // Get the property value stream.

    InputStream binStream = binProp.getStream();

    // You may change the binary property value with a new Stream, all data will be replaced

    // with the content from the new stream.

    Property updatedBinProp = node.setProperty("BinData", newDataStream);

    // Or update an obtained property

    updatedBinProp.setValue(newDataStream);

    // Or update using a Value object

    updatedBinProp.setValue(ValueFactory.createValue(newDataStream));

    // Get the updated property value stream.

    InputStream newStream = updatedBinProp.getStream();

But if you need to update the property sequentially and with partial content, you have no choice but to edit the whole data stream outside and get it back to the repository each time. In case of really large-sized data, the application will be stuck and the productivity will decrease a lot. JCR stream setters will also check constraints and perform common validation each time.

There is a feature of the eXo JCR extension that can be used for binary values partial writing without frequent session level calls. The main idea is to use a value object obtained from the property as the storage of the property content while writing/reading during runtime.

According to the spec JSR-170, Value interface provides the state of property that can't be changed (edited). The eXo JCR core provides ReadableBinaryValue and EditableBinaryValue interfaces which themselves extend JCR Value. The interfaces allow the user to partially read and change a value content.

ReadableBinaryValue value can be casted from any value, i.e. String, Binary, Date etc.

    // get the property value of type PropertyType.STRING

    ReadableBinaryValue extValue = (ReadableBinaryValue) node.getProperty("LargeText").getValue();

    // read 200 bytes to a destStream from the position 1024 in the value content

    OutputStream destStream = new FileOutputStream("MyTextFile.txt");

    extValue.read(destStream, 200, 1024);

But EditableBinaryValue can be applied only to properties of type PropertyType.BINARY. In other cases, a cast to EditableBinaryValue will fail.

After the value has been edited, the EditableBinaryValue value can be applied to the property using the standard setters (Property.setValue(Value), Property.setValues(Value), Node.setProperty(String, Value) etc.). Only after the EditableBinaryValue has been set to the property, it can be obtained in this session by getters (Property.getValue(), Node.getProperty(String) etc.).

The user can obtain an EditableBinaryValue instance and fill it with data in an interaction manner (or any other appropriated to the targets) and return (set) the value to the property after the content will be done.

    // get the property value for PropertyType.BINARY Property

    EditableBinaryValue extValue = (EditableBinaryValue) node.getProperty("BinData").getValue();

    // update length bytes from the stream starting from the position 1024 in existing Value data

    extValue.update(dataInputStream, dataLength, 1024);

    // apply the edited EditableBinaryValue to the Property

    node.setProperty("BinData", extValue);

    // save the Property to persistence

    node.save();

A practical example of the iterative usage. In this example, the value is updated with data from the sequence of streams and after the update is done, the value will be applied to the property and be visible during the session.

    // update length bytes from the stream starting from the particular

    // position in the existing Value data

    int dpos = 1024;

    while (source.dataAvailable()) {

      extValue.update(source.getInputStream(), source.getLength(), dpos);

      dpos = dpos + source.getLength();

    }

    // apply the edited EditableBinaryValue to the Property

    node.setProperty("BinData", extValue);

### 1.41.3. Value implementations

ReadableBinaryValue has one method to read Value.

Read length bytes is counted from the binary value to the given position into the stream.

    long read(OutputStream stream, long length, long position) throws IOException, RepositoryException ;

EditableBinaryValue has two methods to edit value.

Update with length bytes from the specified stream to this value data at a position. If the position is lower than 0, the IOException exception will be thrown. If the position is higher than the current Value length, the Value length will be increased at first to the size of position and length bytes will be added after the position.

    void update(InputStream stream, long length, long position) throws IOException;

Set the length of the Value in bytes to the specified size. If the size is lower than 0, the IOException exception will be thrown. This operation can be used to extend or truncat the Value size. This method is used internally in the update operation in case of extending the size to the given position.

    void setLength(long size) throws IOException;

An application can perform JCR binary operations more flexibly and will have less I/O and CPU usage using these methods.

## 1.42. JCR Resources:

* Java Community Process: JSR 170 and JSR 283

* Tom Wheeler, The Java Content Repository (2007)

* Roy T. Fielding, JSR 170 Overview: Standardizing the Content Repository Interface (March 13, 2005)

* David Nuescheler and Janus Boye, JSR-170 What's in it for me? (April 20, 2005)

* Benjamin Mestrallet, Tuan Nguyen, Gennady Azarenkov, Francois Moron and Brice Revenant eXo Platform v2, Portal, JCR, ECM, Groupware and Business Intelligence (January 2006)

## 1.43. JCR Workspace Data Container (architecture contract)

The goals of this section are:

- Coverage of the requirements of Workspace Data Container implementation

- Description of container life cycle

- Description relations between container and high-level DataManagers

### 1.43.1. Concepts

#### Container and connection

Workspace Data Container (container) serves Repository Workspace persistent storage. WorkspacePersistentDataManager (data manager) uses container to perform CRUD operation on the persistent storage. Accessing to the storage in the data manager is implemented via storage connection obtained from the container (WorkspaceDataContainer interface implemenatiton). Each connection represents a transaction on the storage. Storage Connection (connection) should be an implementation of WorkspaceStorageConnection.

- Container acts as a factory of a new storage connections. Usually, this method is designed to be synchronized to avoid possible concurrent issues.

    WorkspaceStorageConnection openConnection() throws RepositoryException;

- Open read-only WorkspaceStorageConnection. Read-only connections can be potentially a bit faster in some cases.

    WorkspaceStorageConnection openConnection(boolean readOnly) throws RepositoryException;

### *EXPERIMENTAL*

Read-only WorkspaceStorageConnection is experimental feature and not currently handled in JCR. Actually, such connections didn't prove their performance, so JCR Core doesn't use them.

- Storage connection might also be reused. This means reuse of physical resource (e.g. JDBC Connection) allocated by one connection in another. This feature is used in a data manager for saving ordinary and system changes on the system Workspace. But the reuse is an optional feature and it can work, otherwise a new connection will open.

    WorkspaceStorageConnection reuseConnection(WorkspaceStorageConnection original) throws RepositoryException;

- When checking Same-Name Siblings (SNS) existence, JCR Core can use new connection or not. This is defined via Workspace Data Container configuration and retrieved by using a special method.

    boolean isCheckSNSNewConnection();

Container initialization is only based on a configuration. After the container has been created, it's not possible to change parameters. Configuration consists of implementation class and set of properties and Value Storages configuration.

#### Value storages

Container provides optional special mechanism for Value storing. It's possible to configure external Value Storages via container configuration (available only via configuration). Value Storage works as fully independent pluggable storage. All required parameters storage obtains from its configuration. Some storages are possible for one container. Configuration describes such parameters as ValueStoragePluginimplementation class, set of implementation specific properties and filters. The filters declares criteria for Value matching to the storage. Only matched Property Values will be stored. So, in common case, the storage might contains only the part of the Workspace content. Value Storages are very useful for BLOB storing. E.g. storing on the File System instead of a database.

Container obtains Values Storages from ValueStoragePluginProvider component. Provider acts as a factory of Value channels (ValueIOChannel). Channel provides all CRUD operation for Value Storage respecting the transaction manner of work (how it can be possible due to implementation specifics of the storages).

#### Lifecycle

Container is used for read and write operations by data manager. Read operations (getters) uses connection once and close it on the finally. Write operations performs in commit method as a sequence of creating/ updating calls and final commit (or rollback on error). Writes uses one connection (or two - another for system workspace) per commit call. One connection guaranties transaction support for write operations. Commit or rollback should free/clean all resources consumed by the container (connection).

#### Value storage lifecycle

Value storage is used from the container inside. Reads are related to a container reads. Writes are commit-related. Container (connection) implementation should use transaction capabilities of the storages in the same way as for other operations.

### 1.43.2. Requirements

Connection creation and reuse should be a thread safe operation. Connection provides CRUD operations support on the storage.

#### Read operations

- Read ItemData from the storage by item identifier.

    ItemData getItemData(String identifier) throws RepositoryException, IllegalStateException;

- Read ItemData from the storage by using the parent and name of the item, related to the parent location.

    ItemData getItemData(NodeData parentData, QPathEntry name) throws RepositoryException,IllegalStateException;

- Read List of NodeData from the storage by using the parent location of the item.

    List<NodeData> getChildNodesData(NodeData parent) throws RepositoryException, IllegalStateException;

- Reads List of PropertyData from the storage by using the parent location of the item.

    List<PropertyData> getChildPropertiesData(NodeData parent) throws RepositoryException, IllegalStateException;

- Reads List of PropertyData with empty ValueData from the storage by using the parent location of the item.

This methiod specially dedicated for non-content modification operations (e.g. Items delete).

    List<PropertyData> listChildPropertiesData(NodeData parent) throws RepositoryException, IllegalStateException;

- Reads List of PropertyData from the storage by using the parent location of the item.

It's REFERENCE type: Properties referencing Node with given nodeIdentifier. See more in javax.jcr.Node.getReferences()

    List<PropertyData> getReferencesData(String nodeIdentifier) throws RepositoryException,IllegalStateException,UnsupportedOperationException;

#### Write operations

- Add single NodeData.

    void add(NodeData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Add single PropertyData.

    void add(PropertyData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Update NodeData.

    void update(NodeData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Update PropertyData.

    void update(PropertyData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Rename NodeData by using Node identifier and new name and indexing from the data.

    void rename(NodeData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Delete NodeData.

    void delete(NodeData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Delete PropertyData.

    void delete(PropertyData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Persist changes and closes connection. It can be database transaction commit for instance etc.

    void commit() throws IllegalStateException, RepositoryException;

- Refuse persistent changes and closes connection. It can be database transaction rollback for instance etc.

    void rollback() throws IllegalStateException, RepositoryException;

All methods throw IllegalStateException if connection is closed. UnsupportedOperationException if the method is not supported (e.g. JCR Level 1 implementation etc). RepositoryException if some errors occur during preparation, validation or persistence.

#### State operations

- Return true if connection can be used.

    boolean isOpened();

#### Validation of write operations

Container has to care about storage consistency (JCR constraints) on write operations: (InvalidItemStateException should be thrown according the spec). At least, the following checks should be performed:

- On ADD errors

  - Parent not found. Condition: Parent ID (Item with ID is not exists).

  - Item already exists. Condition: ID (Item with ID already exists).

  - Item already exists. Condition: Parent ID, Name, Index (Item with parent ID, name and index already exists).

- On DELETE errors

  - Item not found. Condition ID.

  - Can not delete parent till children exists.

- On UPDATE errors

  - Item not found. Condition ID.

  - Item already exists with higher Version. Condition: ID, Version (Some Session had updated Item with ID prior this update).

#### Consistency of save

The container (connection) should implement consistency of Commit (Rollback) in transaction manner. I.e. If a set of operations was performed before the future Commit and another next operation fails. It should be possible to rollback applied changes using Rollback command.

### 1.43.3. Value storages API

#### Storages provider:

Container implementation obtains Values Storages option via ValueStoragePluginProvider component. Provider acts as a factory of Value channels (ValueIOChannel) and has two methods for this purpose:

- Return ValueIOChannel matched this property and valueOrderNumer. Null will be returned if no channel matches.

    ValueIOChannel getApplicableChannel(PropertyData property, int valueOrderNumer) throws IOException;

- Return ValueIOChannel associated with given storageId.

    ValueIOChannel getChannel(String storageId) throws IOException, ValueStorageNotFoundException;

There is also method for consistency check, but this method doesn't used anywhere and storage implementations has it empty.

#### Value storage plugin

Provider implementation should use ValueStoragePlugin abstract class as a base for all storage implementations. Plugin provides support for provider implementation methods. Plugin's methods should be implemented:

- Initialize this plugin. Used at start time in ValueStoragePluginProvider.

    public abstract void init(Properties props, ValueDataResourceHolder resources) throws RepositoryConfigurationException, IOException;

- Open ValueIOChannel.Used in ValueStoragePluginProvider.getApplicableChannel(PropertyData, int) and getChannel(String)

    public abstract ValueIOChannel openIOChannel() throws IOException;

- Return true if this storage has the same storageId.

    public abstract boolean isSame(String valueDataDescriptor);

#### Value I/O channel

Channel should implement ValueIOChannel interface. CRUD operation for Value Storage:

- Read Property value.

    ValueData read(String propertyId, int orderNumber, int maxBufferSize) throws IOException;

- Add or update Property value.

    void write(String propertyId, ValueData data) throws IOException;

- Delete Property all values.

    void delete(String propertyId) throws IOException;

#### Transaction support via channel

Modification operations should be applied only when commiting. Rollback is required for data created cleanup.

- Commit channel changes.

    void commit() throws IOException;

- Rollback channel changes.

    void rollback() throws IOException;

## 1.44. How to implement Workspace Data Container

To implement Workspace data container, you need to do the following:

1. Read a bit about the contract.

2. Start a new implementation project pom.xml with org.exoplatform.jcr parent. It is not required, but will ease the development.

3. Update sources of JCR Core and read JavaDoc on org.exoplatform.services.jcr.storage.WorkspaceDataContainer and org.exoplatform.services.jcr.storage.WorkspaceStorageConnection interfaces. They are the main part for the implemenation.

4. Look at org.exoplatform.services.jcr.impl.dataflow.persistent.WorkspacePersistentDataManager sourcecode, check how data menager uses container and its connections (see in save() method)

5. Create WorkspaceStorageConnection dummy implementation class. It's freeform class, but to be close to the eXo JCR, check how to implement JDBC or SimpleDB containers ( org.exoplatform.services.jcr.impl.storage.jdbc.JDBCStorageConnection and org.exoplatform.services.jcr.aws.storage.sdb.SDBWorkspaceStorageConnection). Take in account usage of ValueStoragePluginProvider in both implementations.Value storage is an useful option for production versions. But leave it to the end of implementation work.

6. Create the connection implementation unit tests to play TTD. (optional, but takes many benefits for the process)

7. Implement CRUD starting from the read to write etc. Test the methods by using the external implementation ways of data read/write in your backend.

8. When all methods of the connection done start WorkspaceDataContainer. Container class is very simple, it's like a factory for the connections only.

9. Care about container reuseConnection(WorkspaceStorageConnection) method logic. For some backends, it cab be same as openConnection(), but for some others, it's important to reuse physical backend connection, e.g. to be in the same transaction - see JDBC container.

10. It's almost ready to use in data manager. Start another test and go on.

When the container will be ready to run as JCR persistence storage (e.g. for this level testing), it should be configured in Repository configuration.

Assuming that our new implementation class name is org.project.jcr.impl.storage.MyWorkspaceDataContainer.

      <repository-service default-repository="repository">

      <repositories>

        <repository name="repository" system-workspace="production" default-workspace="production">

          .............

          <workspaces>

            <workspace name="production">

              <container class="org.project.jcr.impl.storage.MyWorkspaceDataContainer">

                <properties>

                  <property name="propertyName1" value="propertyValue1" />

                  <property name="propertyName2" value="propertyValue2" />

                  .......

                  <property name="propertyNameN" value="propertyValueN" />

                </properties>

                <value-storages>

                  .......

                </value-storages>

              </container>

Container can be configured by using set properties.

### 1.44.1. Notes on Value storage usage:

Value storages are pluggable to the container but if they are used, the container implementation should respect set of interfaces and external storage usage principles.

If the container has ValueStoragePluginProvider (e.g. via constructor), it's just a few methods to manipulate external Values data.

    // get channel for ValueData write (add or update)

    ValueIOChannel channel = valueStorageProvider.getApplicableChannel(data,  i);

    if (channel == null) {

      // write

      channel.write(data.getIdentifier(),  vd);

      // obtain storage id,  id can be used for linkage of external ValueData and PropertyData in main backend

      String storageId = channel.getStorageId();

    }

    ....

    // delete all Property Values in external storage

    ValueIOChannel channel = valueStorageProvider.getChannel(storageId);

    channel.delete(propertyData.getIdentifier());

    ....

    // read ValueData from external storage

    ValueIOChannel channel = valueStorageProvider.getChannel(storageId);

    ValueData vdata = channel.read(propertyData.getIdentifier(),  orderNumber,  maxBufferSize);

### Important

After a sequence of write and/or delete operations on the storage channel, the channel should be committed (or rolled back on an error). See ValueIOChannel.commit() and ValueIOChannel.rollback() and how those methods are used in JDBC container.

## 1.45. DBCleanService

It is a special service for data removal from database. The section shortly describes the principles of work DBCleaner under all databases.

It is special service for data removal from database. The article shortly describes the principles of work DBCleaner under all databases

### 1.45.1. Methods of DBCleanerService

### Note

Code that invokes the methods of DBCleanService must have JCRRuntimePermissions.MANAGE_REPOSITORY_PERMISSION permission.

There are several methods of DBCleanerService :

public static void cleanWorkspaceData(WorkspaceEntry wsEntry) | Clean workspace data from database
public static void cleanRepositoryData(RepositoryEntry repoEntry) | Cleanup repository data from database
public static DBCleaner getWorkspaceDBCleaner(Connection jdbcConn, WorkspaceEntry wsEntry) | Returns database cleaner of workspace.
public static DBCleaner getRepositoryDBCleaner(Connection jdbcConn, RepositoryEntry repoEntry) | Returns database cleaner of repository. Returns null in case of multi-db configuration.

The cleaning is a part of restoring from backup and it is used in the following restore phases:

clean |
    dbCleaner.executeCleanScripts();
restore | does nothing with DBCleaner
commit |
    dbCleaner.executeCommitScripts();

    connection.commit();
rollback |
    connection.rollback();

    dbCleaner.executeRollbackScripts();

    connection.commit();

Different approaches are used for database cleaning depending on database and JCR configuration.

### 1.45.2. Need to clean only single workspace

Simple cleaning records from JCR table is used in case of single-db configuration.

executeCleanScripts() | removing all records from the database. Foreign key of JCR_SITEM table is also removed
executeCommitScripts() | adding foreign key
executeRollbackScripts() |

executeCleanScripts() | removing all records from the database. Foreign key of JCR_SITEM table is also removed
executeCommitScripts() | adding foreign key
executeRollbackScripts() | adding foreign key

Either removing or renaming JCR tables are used in case of mult-db configuration.

executeCleanScripts() | removing tables JCR_MVALUE, JCR_MREF, JCR_MITEM, initializing new tables without foreign key of JCR_MITEM table, adding root
executeCommitScripts() | adding foreign key
executeRollbackScripts() |

executeCleanScripts() | renaming current tables, initializing new tables without foreign key of JCR_MITEM table, adding root node, removing indexes for some databases
executeCommitScripts() | renaming tables, adding indexes
executeRollbackScripts() | removing previously renamed tables, adding indexes, adding foreign key

### 1.45.3. Need to clean the whole repository

In case of single-db all workspaces will be processed simultaneously as in case of single workspace multi-db configuration. For multi-db every workspace will be processed separately as in case of single workspace multi-db configuration.

## 1.46. JCR Performance Tuning Guide

This section will show you possible ways of improving JCR

It is intended to GateIn Administrators and those who wants to use JCR features.

### 1.46.1. JCR Performance and Scalability

#### Cluster configuration

EC2 network: 1Gbit

Servers hardware:

7.5 GB memory
4 EC2 Compute Units (2 virtual cores with 2 EC2 Compute Units each)
850 GB instance storage (2×420 GB plus 10 GB root partition)
64-bit platform
I/O Performance: High
API name: m1.large

### Note

NFS and statistics (cacti snmp) server were located on one physical server.

JBoss AS configuration

`JAVA_OPTS: -Dprogram.name=run.sh -server -Xms4g -Xmx4g -XX:MaxPermSize=512m -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -XX:+UseParallelGC -Djava.net.preferIPv4Stack=true`

#### JCR Clustered Performance

Benchmark test using webdav (Complex read/write load test (benchmark)) with 20K same file. To obtain per-operation results we have used custom output from the testscase threads to CSV file.

Read operation:

Warm-up iterations: 100
Run iterations: 2000
Background writing threads: 25
Reading threads: 225

Nodes count | tps | Responses >2s | Responses >4s
--- | --- | --- | ---
1 | 523 | 6.87% | 1.27%
2 | 1754 | 0.64% | 0.08%
3 | 2388 | 0.49% | 0.09%
4 | 2706 | 0.46% | 0.1%

Read operaion with more threads:

Warm-up iterations: 100
Run iterations: 2000
Background writing threads: 50
Reading threads: 450

Nodes count | tps | Responses >2s | Responses >4s
--- | --- | --- | ---
1 | 116 | ? | ?
2 | 1558 | 6.1% | 0.6%
3 | 2242 | 3.1% | 0.38%
4 | 2756 | 2.2% | 0.41%

### 1.46.2. Performance Tuning Guide

#### JBoss AS Tuning

You can use `maxThreads` parameter to increase maximum amount of threads that can be launched in AS instance. This can improve performance if you need a high level of concurrency. also you can use `-XX:+UseParallelGC` java directory to use paralel garbage collector.

### Tip

Beware of setting `maxThreads` too big, this can cause `OutOfMemoryError`. We've got it with `maxThreads=1250` on such machine:

7.5 GB memory
4 EC2 Compute Units (2 virtual cores with 2 EC2 Compute Units each)
850 GB instance storage (2×420 GB plus 10 GB root partition)
64-bit platform
I/O Performance: High
API name: m1.large
java -Xmx 4g

#### JCR Cache Tuning

Cache size

JCR-cluster implementation is built using JBoss Cache as distributed, replicated cache. But there is one particularity related to remove action in it. Speed of this operation depends on the actual size of cache. As many nodes are currently in cache as much time is needed to remove one particular node (subtree) from it.

Eviction

Manipulations with eviction `wakeUpInterval` value doestn't affect on performance. Performance results with values from 500 up to 3000 are approximately equal.

Transaction Timeout

Using short timeout for long transactions such as Export/Import, removing huge subtree defined timeout may cause `TransactionTimeoutException`. [TODO] put recomended timeout value

#### Clustering

For performance it is better to have loadbalacer, DB server and shared NFS on different computers. If in some reasons you see that one node gets more load than others you can decrease this load using load value in load balancer.

JGroups configuration

It's recommended to use "multiplexer stack" feature present in JGroups. It is set by default in eXo JCR and offers higher performance in cluster, using less network connections also. If there are two or more clusters in your network, please check that they use different ports and different cluster names.

Write performance in cluster

Exo JCR implementation uses Lucene indexing engine to provide search capabilities. But Lucene brings some limitations for write operations: it can perform indexing only in one thread. Thats why write performance in cluster is not higher than in singleton environment. Data is indexed on coordinator node, so increasing write-load on cluster may lead to ReplicationTimeout exception. It occurs because writing threads queue in the indexer and under high load timeout for replication to coordinator will be exceeded.

Taking in consideration this fact, it is recommended to exceed `replTimeout` value in cache configurations in case of high write-load.

Replication timeout

Some operations may take too much time. So if you get `ReplicationTimeoutException` try increasing replication timeout:

       <clustering mode="replication" clusterName="${jbosscache-cluster-name}">

          ...

          <sync replTimeout="60000" />

       </clustering>

value is set in miliseconds.

#### JVM parameters

PermGen space size

If you intend to use Infinispan, you will have to increase the PermGen size to at least 256 Mo due to the latest versions of JGroups that are needed by Infinispan (please note that Infinspan is only dedicated to the community for now, no support will be provided). In case, you intend to use JBoss Cache, you can keep on using JGroups 2.6.13.GA which means that you don't need to increase the PermGen size.

Copyright © 2009-2012. All rights reserved. eXo Platform SAS