JBoss.orgCommunity Documentation

eXoJCR Reference Manual

Java Content Repository and Extension services


I. eXoJCR
1. Introduction in eXoJCR
1.1. JCR (JSR-170) API main concepts
1.1.1. Data model
2. Basic concepts of eXoJCR
3. eXo JCR configuration
3.1. Related documents
3.2. Portal and Standalone configuration
3.2.1. JCR Configuration
3.2.2. Repository service configuration
4. JDBC Data Container Config
4.1. Introduction
4.2. Multi-database Configuration
4.3. Single-database configuration
4.3.1. Configuration without DataSource
4.3.2. Dynamic Workspace Creation
4.4. Simple and Complex queries
4.5. Forse Query Hints
4.6. Notes for Microsoft Windows users
5. External Value Storages
5.1. Introduction
5.2. Tree File Value Storage
5.3. Simple File Value Storage
5.4. Content Addressable Value storage (CAS) support
6. Search Configuration
6.1. XML Configuration
6.2. Configuration parameters
6.3. Global Search Index
6.3.1. Global Search Index Configuration
6.3.2. Customized Search Indexes and Analyzers
6.4. Index Adjustments
6.4.1. IndexingConfiguration
6.4.2. Index rules
6.4.3. Index Aggregates
6.4.4. Property-Level Analyzers
7. Multilanguage support in eXo JCR RDB backend
7.1. Intro
7.2. Oracle
7.3. DB2
7.4. MySQL
7.5. PostgreSQL
8. JCR Configuration persister
8.1. Idea
8.2. Usage
9. Configuring JBoss AS with eXo JCR in cluster
9.1. Launching Cluster
9.1.1. Deploying eXo JCR to JBoss As
9.1.2. Configuring JCR to use external configuration
9.2. Requirements
9.2.1. Enviorenment requirements
9.2.2. Enviorenment requirements
10. JBoss Cache configuration
10.1. JBoss cache configuration for indexer, lock manager and data container
10.2. JGroups configuration
10.3. Shipped JBoss Cache configuration templates
10.3.1. Data container template
10.3.2. Lock manager template
10.3.3. Query handler (indexer) template
11. LockManager configuration
11.1. Introduction
11.2. LockManagerImpl
11.3. CacheableLockManagerImpl
11.3.1. Configuration
11.3.2. Simple JbossCache Configuraion
11.3.3. Template JBossCache Configuration
11.3.4. Data Types in Different Databases
12. QueryHandler configuration
12.1. How does it work?
12.2. Configuration
12.2.1. Common requirements
12.2.2. Query-handler configuration
12.2.3. JBoss-Cache template configuration
13. JBossTransactionsService
13.1. Introduction
13.2. Configuration
14. TransactionManagerLookup
14.1. Configuration
15. eXo JCR statistics
15.1. Statistics on the Database Access Layer
15.2. Statistics on the JCR API accesses
15.3. Statistics Manager
II. eXoKernel
16. eXo Kernel
16.1. eXo Kernel introduction
17. Configuration
17.1. Kernel configuration namespace
17.2. Understanding How configuration files are loaded
17.2.1. Configuration Retrieval
17.2.2. Advanced concepts for the PortalContainers
17.3. System property configuration
17.3.1. Properties init param
17.3.2. Properties URL init param
17.3.3. System Property configuration of the properties URL
17.4. Runtime configuration profiles
17.4.1. Profiles activation
17.4.2. Profiles configuration
17.5. Component request life cycle
17.5.1. Component request life cycle contract
17.5.2. Request life cycle
17.5.3. When request life cycle is triggered
18. eXo Cache
18.1. Basic concepts
18.2. eXo Cache extension
18.3. eXo Cache based on JBoss Cache
18.3.1. Configure the ExoCacheFactory
18.3.2. Add specific configuration for a cache
18.3.3. Add a cache creator
18.3.4. Define a cache
19. TransactionService
19.1. Base information
20. JNDI naming
20.1. Prerequisites
20.2. How it works
20.2.1. JNDI System property initialization
20.2.2. JNDI reference binding
20.3. Configuration examples
20.4. Recommendations for Application Developers
20.5. InitialContextInitializer API
21. Logs configuration
21.1. Introdution
21.2. Logs configuration initializer
21.3. Configuration examples
21.3.1. Log4J
21.3.2. JDK Logging
21.3.3. Commons Logging SimpleLog
21.4. Tips and Troubleshooting
21.4.1. JBoss tips
21.4.2. Other tips
III. eXoCore
22. eXo Core
22.1. eXo Core introduction
23. Database Creator
23.1. About
23.2. API
23.3. A configuration examples
23.4. An examples of a DDL script
IV. eXoWS
24. eXo Web Services
24.1. eXo Web Services introduction
25. Introduction to the Representational State Transfer (REST)
25.1. Introduction
26. REST Service Tutorial
26.1. Introduction
26.2. Source code
26.3. Configuration
26.4. Build
26.5. Deploy
26.6. Usage
27. REST Migration to jsr311
27.1. REST service structure:
27.2. Migration Examples
27.3. Support for javax.ws.rs.core.Application
28. Groovy Scripts as REST Services
28.1. Overview
28.2. Loading script and save it in JCR
28.3. Instantiation
28.4. Deploy newly created Class as RESTful service
28.5. Script Lifecycle Management
28.6. Get node UUID example
28.7. Groovy script restrictions
29. REST Framework
29.1. Requirements
29.2. Implementation
29.2.1. ResourceContainer
29.2.2. Response
29.2.3. Transformer
29.2.4. Binding and unbinding components (ResourceContainers)
29.2.5. ResourceDispatcher
30. SOAP Service Tutorial
30.1. Introduction
30.2. Add JSR 181 support to the deployed application
30.2.1. Add dependencies
30.2.2. Configure CXF servlet
30.3. Write the Web Service class
30.4. Configure the component
30.5. Verify it works.
31. Central Authentication Service Configuration
31.1. Introduction
31.2. CAS configuration
31.2.1. Configure CAS server
31.2.2. Configure other tomcat instance
31.2.3. Usage
32. Kerberos SSO on Active Directory
32.1. Overview
32.2. Active Directory configuration
32.3. Setup webserver
33. oAuth
33.1. Overview
33.2. Provider
33.2.1. Configuration
33.2.2. Servlets
33.3. Consumer
33.3.1. How it works
33.3.2. web.xml
33.3.3. Stages
33.3.4. Token Alive Time
33.3.5. Token Cleaner Timeout
34. Cometd
34.1. definition
34.2. Use
34.3. Sample
34.4. improvements
35. Cometd cluster
35.1. Motivation
35.2. How it works
35.3. Bench
36. Framework for cross-domain AJAX
36.1. Motivation
36.2. Scheme (how it works)
36.3. A Working Sequence:
36.4. How to use it
37. Cometd Cluster Bench
37.1. How we test
37.2. Environment
37.3. Result
38. JavaScript WebDAV Library
38.1. Introduction to the JavaScript WebDAV library
38.2. Content of JavaScript WebDAV Library:
38.3. Example of using of JavaScript WebDAV library
38.4. How to get JavaScript WebDAV library
39. eXo WS 2.0 Release Notes (released on 2009.02.07)
39.1. Notable Changes
39.2. API changes
39.3. Dependencies changed
39.4. How to upgrade
39.5. System Requirements
39.6. Detailed Changelog
39.7. Download
40. eXo WS 2.0.1 Release Notes (released on 2009.04.16)
40.1. Notable Changes
40.2. API changes
40.3. Dependencies changed
40.4. How to upgrade
40.5. System Requirements
40.6. Detailed Changelog

Table of Contents

1. Introduction in eXoJCR
1.1. JCR (JSR-170) API main concepts
1.1.1. Data model
2. Basic concepts of eXoJCR
3. eXo JCR configuration
3.1. Related documents
3.2. Portal and Standalone configuration
3.2.1. JCR Configuration
3.2.2. Repository service configuration
4. JDBC Data Container Config
4.1. Introduction
4.2. Multi-database Configuration
4.3. Single-database configuration
4.3.1. Configuration without DataSource
4.3.2. Dynamic Workspace Creation
4.4. Simple and Complex queries
4.5. Forse Query Hints
4.6. Notes for Microsoft Windows users
5. External Value Storages
5.1. Introduction
5.2. Tree File Value Storage
5.3. Simple File Value Storage
5.4. Content Addressable Value storage (CAS) support
6. Search Configuration
6.1. XML Configuration
6.2. Configuration parameters
6.3. Global Search Index
6.3.1. Global Search Index Configuration
6.3.2. Customized Search Indexes and Analyzers
6.4. Index Adjustments
6.4.1. IndexingConfiguration
6.4.2. Index rules
6.4.3. Index Aggregates
6.4.4. Property-Level Analyzers
7. Multilanguage support in eXo JCR RDB backend
7.1. Intro
7.2. Oracle
7.3. DB2
7.4. MySQL
7.5. PostgreSQL
8. JCR Configuration persister
8.1. Idea
8.2. Usage
9. Configuring JBoss AS with eXo JCR in cluster
9.1. Launching Cluster
9.1.1. Deploying eXo JCR to JBoss As
9.1.2. Configuring JCR to use external configuration
9.2. Requirements
9.2.1. Enviorenment requirements
9.2.2. Enviorenment requirements
10. JBoss Cache configuration
10.1. JBoss cache configuration for indexer, lock manager and data container
10.2. JGroups configuration
10.3. Shipped JBoss Cache configuration templates
10.3.1. Data container template
10.3.2. Lock manager template
10.3.3. Query handler (indexer) template
11. LockManager configuration
11.1. Introduction
11.2. LockManagerImpl
11.3. CacheableLockManagerImpl
11.3.1. Configuration
11.3.2. Simple JbossCache Configuraion
11.3.3. Template JBossCache Configuration
11.3.4. Data Types in Different Databases
12. QueryHandler configuration
12.1. How does it work?
12.2. Configuration
12.2.1. Common requirements
12.2.2. Query-handler configuration
12.2.3. JBoss-Cache template configuration
13. JBossTransactionsService
13.1. Introduction
13.2. Configuration
14. TransactionManagerLookup
14.1. Configuration
15. eXo JCR statistics
15.1. Statistics on the Database Access Layer
15.2. Statistics on the JCR API accesses
15.3. Statistics Manager

Like other eXo services eXo JCR can be configured and used in 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 (TODO see the comprehensive page Service Configuration for Beginners for more details).

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

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

Default configuration of the Repository Service located in jar:/conf/portal/exo-jcr-config.xml, it will be available for portal and standalone modes.

In portal mode it is overriden and located in the portal web application portal/WEB-INF/conf/jcr/repository-configuration.xml.

Example of Repository Service configuration for standalone mode:

<repository-service default-repository="repository">
   <repositories>
      <repository name="db1" system-workspace="ws" default-workspace="ws">
         <security-domain>exo-domain</security-domain>
         <access-control>optional</access-control>
         <session-max-age>1h</session-max-age>
         <authentication-policy>org.exoplatform.services.jcr.impl.core.access.JAASAuthenticator</authentication-policy>
         <workspaces>
            <workspace name="production">
               <!-- for system storage -->
               <container class="org.exoplatform.services.jcr.impl.storage.jdbc.optimisation.CQJDBCWorkspaceDataContainer">
                  <properties>
                     <property name="source-name" value="jdbcjcr" />
                     <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>
                     <value-storage id="system" class="org.exoplatform.services.jcr.impl.storage.value.fs.TreeFileValueStorage">
                        <properties>
                           <property name="path" value="../temp/values/production" />
                        </properties>
                        <filters>
                           <filter property-type="Binary" />
                        </filters>
                     </value-storage>
                  </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.LinkedWorkspaceStorageCacheImpl">
                  <properties>
                     <property name="max-size" value="10k" />
                     <property name="live-time" value="1h" />
                  </properties>
               </cache>
               <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">
                  <properties>
                     <property name="index-dir" value="../temp/jcrlucenedb/production" />
                  </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="../temp/lock/system" />
                     </properties>
                  </persister>
               </lock-manager>
            </workspace>

            <workspace name="backup">
               <container class="org.exoplatform.services.jcr.impl.storage.jdbc.optimisation.CQJDBCWorkspaceDataContainer">
                  <properties>
                     <property name="source-name" value="jdbcjcr" />
                     <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/backup" />
                  </properties>
                  <value-storages>
                     <value-storage id="draft" class="org.exoplatform.services.jcr.impl.storage.value.fs.TreeFileValueStorage">
                        <properties>
                           <property name="path" value="../temp/values/backup" />
                        </properties>
                        <filters>
                           <filter property-type="Binary" />
                        </filters>
                     </value-storage>
                  </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.LinkedWorkspaceStorageCacheImpl">
                  <properties>
                     <property name="max-size" value="10k" />
                     <property name="live-time" value="1h" />
                  </properties>
               </cache>
               <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">
                  <properties>
                     <property name="index-dir" value="../temp/jcrlucenedb/backup" />
                  </properties>
               </query-handler>
            </workspace>
          </workspaces>
       </repository>
   </repositories>
</repository-service>

Repository Service configuration:

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

repositories - the list of repositories

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 - 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 not set, the idle session will never be removed.

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

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

value-storages - the list of value storage plugins

Value Storage plugin configuration (optional feature):

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

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.

Cache configuration:

enabled - if workspace cache is enabled

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.

live-time - cached item live time.

LinkedWorkspaceStorageCacheImpl supports additional optional parameters TODO

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*

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.

Configuration definition:

<!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>

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

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 RDBMS:

Each database software supports ANSI SQL standards but has its own specifics too. 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.

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.

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.

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:

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

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

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 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)

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

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").

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).

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 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>

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

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.

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.

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

To enable CAS support just configure it in JCR Repositories configuration like 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 same as in workspace container;

Table 6.1. 

ParameterDefaultDescriptionSince
index-dirnoneThe location of the index directory. This parameter is mandatory. Up to 1.9 this parameter called "indexDir"1.0
use-compoundfiletrueAdvises lucene to use compound files for the index files.1.9
min-merge-docs100Minimum number of nodes in an index until segments are merged.1.9
volatile-idle-time3Idle time in seconds until the volatile index part is moved to a persistent index even though minMergeDocs is not reached.1.9
max-merge-docsInteger.MAX_VALUEMaximum number of nodes in segments that will be merged. The default value changed in JCR 1.9 to Integer.MAX_VALUE.1.9
merge-factor10Determines how often segment indices are merged.1.9
max-field-length10000The number of words that are fulltext indexed at most per property.1.9
cache-size1000Size of the document number cache. This cache maps uuids to lucene document numbers1.9
force-consistencycheckfalseRuns 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-repairtrueErrors detected by a consistency check are automatically repaired. If false, errors are only written to the log.1.9
query-classQueryImplClass 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-ordertrueIf 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-sizeInteger.MAX_VALUEThe number of results when a query is executed. Default value: Integer.MAX_VALUE (-> all).1.9
excerptprovider-classDefaultXMLExcerptThe 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-highlightingfalseIf set to true additional information is stored in the index to support highlighting using the rep:excerpt() function.1.9
synonymprovider-classnoneThe 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-pathnoneThe path to the synonym provider configuration file. This path interpreted relative to the path parameter. If there is a path element inside the SearchIndex element, then this path is interpreted relative to the root path of the path. Whether this parameter is mandatory depends on the synonym provider implementation. The default value is null (-> not set).1.9
indexing-configuration-pathnoneThe path to the indexing configuration file.1.9
indexing-configuration-classIndexingConfigurationImplThe name of the class that implements org.exoplatform.services.jcr.impl.core.query.lucene.IndexingConfiguration.1.9
force-consistencycheckfalseIf set to true a consistency check is performed depending on the parameter forceConsistencyCheck. If set to false no consistency check is performed on startup, even if a redo log had been applied.1.9
spellchecker-classnoneThe name of a class that implements org.exoplatform.services.jcr.impl.core.query.lucene.SpellChecker.1.9
errorlog-size50(Kb)The default size of error log file in Kb.1.9
upgrade-indexfalseAllows 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
analyzerorg.apache.lucene.analysis.standard.StandardAnalyzerClass name of a lucene analyzer to use for fulltext indexing of text.1.12

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;
  }

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.

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 remaining 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 reference 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>

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 index aggregates based on relative path patterns and primary node types.

The following example creates an index 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>

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!

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's 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

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>
          .....

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.

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>
          .....

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 any language, 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>
          .....

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:

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.

To deploy eXo JCR to JBoss As follow next steps:

  1. Dowload the latest version of eXo JCR ear 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://localhost:8080/browser with root/exo as login/password if you have done everything right, you'll get access to repository browser.

  • To manually configure repository create a new configuration file (f.e. 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 "Value storage configuration" 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  "Cache configuration" part.
                   </cache>
                   <query-handler class="org.exoplatform.services.jcr.impl.core.query.lucene.SearchIndex">
                      see  "Indexer configuration" part.
                   </query-handler>
                   <lock-manager class="org.exoplatform.services.jcr.impl.core.lock.jbosscache.CacheableLockManagerImpl">
                      see  "Lock Manager configuration" part.
                   </lock-manager>
                </workspace>
                <workspace name="ws2">
                            ...
                </workspace>
                <workspace name="wsN">
                            ...
                </workspace>
             </workspaces>
          </repository>
       </repositories>
    </repository-service> 
  • and 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>

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

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.

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="50000" lockParentForChildInsertRemove="false"
      lockAcquisitionTimeout="20000" />
   <clustering mode="replication" clusterName="${jbosscache-cluster-name}">
      <stateRetrieval timeout="20000" fetchInMemoryState="false" />
      <jgroupsConfig multiplexerStack="jcr.stack" />
      <sync />
   </clustering>
   <loaders passivation="false" shared="true">
      <preload>
         <node fqn="/" />
      </preload>
      <loader class="org.jboss.cache.loader.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>

CacheableLockManagerImpl stores Lock object in JBoss-cache, so Locks are replicable and affects on cluster, not only a single node. Also JBoss-cache has JDBCCacheLoader, so locks will be stored to database.

Both implementation supports Expired Locks removing. There is LockRemover - separate thread, that periodically ask LockManager for Locks that lives to much and must be removed. So, timeout for LockRemover may be set as follows, default value is 30m.

<properties>
   <property name="time-out" value="10m" />
   ...
</properties>

Replication requirements are same as for Cache

Full JCR configuration example you can see here.

Common tips:

  • clusterName ("jbosscache-cluster-name") must be unique;

  • cache.jdbc.table.name must be unique per datasource;

  • cache.jdbc.fqn.type must and cache.jdbc.node.type must be configured according to used database;

There is few ways how to configure CacheableLockManagerImpl, and all of them configures JBoss-cache and JDBCCacheLoader.

See http://community.jboss.org/wiki/JBossCacheJDBCCacheLoader

First one is - put JbossCache configuraion file path to CacheableLockManagerImpl

Config is:

<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="50000" lockParentForChildInsertRemove="false" lockAcquisitionTimeout="20000" />

 <clustering mode="replication" clusterName="JBoss-Cache-Lock-Cluster_Name">
  <stateRetrieval timeout="20000" fetchInMemoryState="false" nonBlocking="true" />
  <jgroupsConfig>

   <TCP bind_addr="127.0.0.1" start_port="9800" loopback="true" recv_buf_size="20000000" send_buf_size="640000" discard_incompatible_packets="true"
    max_bundle_size="64000" max_bundle_timeout="30" use_incoming_packet_handler="true" enable_bundling="false" use_send_queues="false" sock_conn_timeout="300"
    skip_suspected_members="true" use_concurrent_stack="true" thread_pool.enabled="true" thread_pool.min_threads="1" thread_pool.max_threads="25"
    thread_pool.keep_alive_time="5000" thread_pool.queue_enabled="false" thread_pool.queue_max_size="100" thread_pool.rejection_policy="run"
    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" />
   <MPING timeout="2000" num_initial_members="2" mcast_port="34540" bind_addr="127.0.0.1" mcast_addr="224.0.0.1" />


   <MERGE2 max_interval="30000" min_interval="10000" />
   <FD_SOCK />
   <FD max_tries="5" shun="true" timeout="10000" />
   <VERIFY_SUSPECT timeout="1500" />
   <pbcast.NAKACK discard_delivered_msgs="true" gc_lag="0" retransmit_timeout="300,600,1200,2400,4800" use_mcast_xmit="false" />
   <UNICAST timeout="300,600,1200,2400,3600" />
   <pbcast.STABLE desired_avg_gossip="50000" max_bytes="400000" stability_delay="1000" />
   <pbcast.GMS join_timeout="5000" print_local_addr="true" shun="false" view_ack_collection_timeout="5000" view_bundling="true" />
   <FRAG2 frag_size="60000" />
   <pbcast.STREAMING_STATE_TRANSFER />
  <pbcast.FLUSH timeout="0" />

  </jgroupsConfig

  <sync />
 </clustering>

 <loaders passivation="false" shared="true">
  <preload>
   <node fqn="/" />
  </preload>
  <loader class="org.jboss.cache.loader.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:

Second one is - use 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="50000" lockParentForChildInsertRemove="false"
      lockAcquisitionTimeout="20000" />

   <clustering mode="replication" clusterName="${jbosscache-cluster-name}">
      <stateRetrieval timeout="20000" fetchInMemoryState="false" />
      <jgroupsConfig multiplexerStack="jcr.stack" />
      <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.jboss.cache.loader.JDBCCacheLoader" async=q"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 paramaters filled by templates and will be replaced by LockManagers conf 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="true" />
      <property name="jbosscache-cluster-name" value="JCR-cluster-locks-ws" />
      <property name="jbosscache-cl-cache.jdbc.table.name" value="jcrlocks_ws" />
      <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_ws_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" />
   </properties>
</lock-manager>

Configuration requirements:

our-udp-mux.xml

<protocol_stacks>
   <stack name="jcr.stack">
      <config>
         <UDP mcast_addr="228.10.10.10" 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="2" enable_bundling="true" 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>
   </stack>
</protocol_stacks> 

Lets talk about indexing content in cluster.

For couple of reasons, we can't replicate index. That's means, some data added and indexed on one cluster node, will be replicated to another cluster node, but will not be indexed on that node.

So, how do the indexing works in cluster environment?

As, we can not index same data on all nodes of cluster, we must index it on one node. Node, that can index data and do changes on lucene index, is called "coordinator". Coordinator-node is choosen automaticaly, so we do not need special configuration for coordinator.

But, how can another nodes save their changes to lucene index?

First of all, data is already saved and replicated to another cluster-nodes, so we need only deliver message like "we need to index this data" to coordinator. Thats why Jboss-cache is used.

All nodes of cluster writes messages into JBoss-cache but only coordinator takes those messages and makes changes Lucene index.

How do the search works in cluster environment?

Search engine do not works with indexer, coordinator, etc. Search needs only lucene index. But only one cluster node can change lucene index - asking you. Yes - lucene index is shared. So, all cluster nodes must be configured to use lucene index from shared directory.

A little bit about indexing process (no matter, cluster or not) Indexer do not writes changes to FS lucene index immediately. At first, Indexer writes changes to Volatile index. If Volatile index size become 1Mb or more it is flushed to FS. Also there is timer, that flushes volatile index by timeout. Volatile index timeout configured by "max-volatile-time" paremeter.

See more about Search Configuration.

Common scheme of Shared Index

In order to have a better idea of the time spent into the database access layer, it cans 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 anormally 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:

Those figures are also available globaly 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.


In order to know exactly how your application uses eXo JCR, it cans 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 into the configuration which part of eXo JCR needs to be monitored whitout applying any changes in your code and/or building anything, we choosed 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:

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 statistcs 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.exoplaform.org/xml/ns/kernel_1_0.xsd http://www.exoplaform.org/xml/ns/kernel_1_0.xsd"
 xmlns="http://www.exoplaform.org/xml/ns/kernel_1_0.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 to 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 also 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.

Please note that this feature will affect the performances of eXo JCR so it must be used with caution.

The statistics manager manages all the statistics provided by eXo JCR, it is responsible of printing the data into the CSV files but also to expose 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.


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:

Table 15.3. JMX Methods

getMinGives 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 the statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
getMaxGives 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 the statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
getTotalGives 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 the statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
getAvgGives 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 the statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
getTimesGives 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 the statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
resetReset the statistics for the given category name and statistics name. The expected arguments are the name of the category of the statistics (e.g. JDBCStorageConnection) and the name of the expected method or global for the global value.
resetAllReset all the statistics for the given category name. The expected argument is the name of the category of the statistics (e.g. JDBCStorageConnection).


The full name of the related MBean is exo:service=statistic, view=jcr.

To be effective the namespace URI http://www.exoplaform.org/xml/ns/kernel_1_1.xsd must be target namespace of the XML configuration file.

<xsd:schema
     targetNamespace="http://www.exoplaform.org/xml/ns/kernel_1_1.xsd"
     xmlns="http://www.exoplaform.org/xml/ns/kernel_1_1.xsd"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     elementFormDefault="qualified"
     attributeFormDefault="unqualified"
     version="1.0">

   ...
</xsd:schema>

eXo Portal uses PicoContainer, which implements the Inversion of Control (IoC) design pattern. All eXo containers inherit from a PicoContainer. There are mainly two eXo containers used, each of them can provide one or several services. Each container service is delivered in a JAR file. This JAR file may contain a default configuration. The use of default configurations is recommended and most services provide it.

When a Pico Container searches for services and its configurations, each configurable service may be reconfigured to override default values or set additional parameters. If the service is configured in two or more places the configuration override mechanism will be used.

The container performs the following steps making eXo Container configuration retrieval depending on the container type.

After the processing of all configurations available in system the container will initialize it and start each service in order of the dependency injection (DI).

The user/developer should be careful when configuring the same service in different configuration files. It's recommended to configure a service in its own JAR only. Or, in case of a portal configuration, strictly reconfigure the services in portal WAR files or in an external configuration.

There are services that can be (or should be) configured more than one time. This depends on business logic of the service. A service may initialize the same resource (shared with other services) or may add a particular object to a set of objects (shared with other services too). In the first case it's critical who will be the last, i.e. whose configuration will be used. In the second case it's no matter who is the first and who is the last (if the parameter objects are independent).

Since eXo JCR 1.12, we added a set of new features that have been designed to extend portal applications such as GateIn.

Now we can define precisely a portal container and its dependencies and settings thanks to the PortalContainerDefinition that currently contains the name of the portal container, the name of the rest context, the name of the realm he web application dependencies ordered by loading priority (i.e. the first dependency must be loaded at first and so on..) and the settings.

To be able to define a PortalContainerDefinition, we need to ensure first of all that a PortalContainerConfig has been defined at the RootContainer level, see below an example:

  <component>
    <!-- The full qualified name of the PortalContainerConfig -->
    <type>org.exoplatform.container.definition.PortalContainerConfig</type>
    <init-params>
      <!-- The name of the default portal container -->
      <value-param>
        <name>default.portal.container</name>
        <value>myPortal</value>
      </value-param>
      <!-- The name of the default rest ServletContext -->
      <value-param>
        <name>default.rest.context</name>
        <value>myRest</value>
      </value-param>
      <!-- The name of the default realm -->
      <value-param>
        <name>default.realm.name</name>
        <value>my-exo-domain</value>
      </value-param>
      <!-- The default portal container definition -->
      <!-- It cans be used to avoid duplicating configuration -->
      <object-param>
        <name>default.portal.definition</name>
        <object type="org.exoplatform.container.definition.PortalContainerDefinition">
          <!-- All the dependencies of the portal container ordered by loading priority -->
          <field name="dependencies">
            <collection type="java.util.ArrayList">
              <value>
                <string>foo</string>
              </value>
              <value>
                <string>foo2</string>
              </value>
              <value>
                <string>foo3</string>
              </value>
            </collection>
          </field>        
          <!-- A map of settings tied to the default portal container -->
          <field name="settings">
            <map type="java.util.HashMap">
              <entry>
                <key>
                  <string>foo5</string>
                </key>
                <value>
                  <string>value</string>
                </value>
              </entry>
              <entry>
                <key>
                  <string>string</string>
                </key>
                <value>
                  <string>value0</string>
                </value>
              </entry>
              <entry>
                <key>
                  <string>int</string>
                </key>
                <value>
                  <int>100</int>
                </value>
              </entry>
            </map>
          </field>
          <!-- The path to the external properties file -->
          <field name="externalSettingsPath">
            <string>classpath:/org/exoplatform/container/definition/default-settings.properties</string>
          </field>
        </object>
      </object-param>
    </init-params>
  </component>

A new PortalContainerDefinition can be defined at the RootContainer level thanks to an external plugin, see below an example:

  <external-component-plugins>
    <!-- The full qualified name of the PortalContainerConfig -->
    <target-component>org.exoplatform.container.definition.PortalContainerConfig</target-component>
    <component-plugin>
      <!-- The name of the plugin -->
      <name>Add PortalContainer Definitions</name>
      <!-- The name of the method to call on the PortalContainerConfig in order to register the PortalContainerDefinitions -->
      <set-method>registerPlugin</set-method>
      <!-- The full qualified name of the PortalContainerDefinitionPlugin -->
      <type>org.exoplatform.container.definition.PortalContainerDefinitionPlugin</type>
      <init-params>
        <object-param>
          <name>portal</name>
          <object type="org.exoplatform.container.definition.PortalContainerDefinition">
            <!-- The name of the portal container -->
            <field name="name">
              <string>myPortal</string>
            </field>
            <!-- The name of the context name of the rest web application -->
            <field name="restContextName">
              <string>myRest</string>
            </field>
            <!-- The name of the realm -->
            <field name="realmName">
              <string>my-domain</string>
            </field>
            <!-- All the dependencies of the portal container ordered by loading priority -->
            <field name="dependencies">
              <collection type="java.util.ArrayList">
                <value>
                  <string>foo</string>
                </value>
                <value>
                  <string>foo2</string>
                </value>
                <value>
                  <string>foo3</string>
                </value>
              </collection>
            </field>
            <!-- A map of settings tied to the portal container -->
            <field name="settings">
              <map type="java.util.HashMap">
                <entry>
                  <key>
                    <string>foo</string>
                  </key>
                  <value>
                    <string>value</string>
                  </value>
                </entry>
                <entry>
                  <key>
                    <string>int</string>
                  </key>
                  <value>
                    <int>10</int>
                  </value>
                </entry>
                <entry>
                  <key>
                    <string>long</string>
                  </key>
                  <value>
                    <long>10</long>
                  </value>
                </entry>
                <entry>
                  <key>
                    <string>double</string>
                  </key>
                  <value>
                    <double>10</double>
                  </value>
                </entry>
                <entry>
                  <key>
                    <string>boolean</string>
                  </key>
                  <value>
                    <boolean>true</boolean>
                  </value>
                </entry>                                
              </map>
            </field>            
            <!-- The path to the external properties file -->
            <field name="externalSettingsPath">
              <string>classpath:/org/exoplatform/container/definition/settings.properties</string>
            </field>
          </object>
        </object-param>
      </init-params>
    </component-plugin>
  </external-component-plugins>

Table 17.2. Descriptions of the fields of PortalContainerDefinition when it is used to define a new portal container

nameThe name of the portal container. This field is mandatory .
restContextNameThe name of the context name of the rest web application. This field is optional. The default value will the value define at the PortalContainerConfig level.
realmNameThe name of the realm. This field is optional. The default value will the value define at the PortalContainerConfig level.
dependenciesAll the dependencies of the portal container ordered by loading priority. This field is optional. The default value will the value define at the PortalContainerConfig level. The dependencies are in fact the list of the context names of the web applications from which the portal container depends. This field is optional. The dependency order is really crucial since it will be interpreted the same way by several components of the platform. All those components, will consider the 1st element in the list less important than the second element and so on. It is currently used to:
  • Know the loading order of all the dependencies.

  • If we have several PortalContainerConfigOwner

    • The ServletContext of all the PortalContainerConfigOwner will be unified, if we use the unified ServletContext (PortalContainer.getPortalContext()) to get a resource, it will try to get the resource in the ServletContext of the most important PortalContainerConfigOwner (i.e. last in the dependency list) and if it cans find it, it will try with the second most important PortalContainerConfigOwner and so on.

    • The ClassLoader of all the PortalContainerConfigOwner will be unified, if we use the unified ClassLoader (PortalContainer.getPortalClassLoader()) to get a resource, it will try to get the resource in the ClassLoader of the most important PortalContainerConfigOwner (i.e. last in the dependency list) and if it cans find it, it will try with the second most important PortalContainerConfigOwner and so on.

settingsA java.util.Map of internal parameters that we would like to tie the portal container. Those parameters could have any type of value. This field is optional. If some internal settings are defined at the PortalContainerConfig level, the two maps of settings will be merged. If a setting with the same name is defined in both maps, it will keep the value defined at the PortalContainerDefinition level.
externalSettingsPathThe path of the external properties file to load as default settings to the portal container. This field is optional. If some external settings are defined at the PortalContainerConfig level, the two maps of settings will be merged. If a setting with the same name is defined in both maps, it will keep the value defined at the PortalContainerDefinition level. The external properties files can be either of type "properties" or of type "xml". The path will be interpreted as follows:
  1. The path doesn't contain any prefix of type "classpath:", "jar:" or "file:", we assume that the file could be externalized so we apply the following rules:

    1. A file exists at ${exo-conf-dir}/portal/${portalContainerName}/${externalSettingsPath}, we will load this file.

    2. No file exists at the previous path, we then assume that the path cans be interpreted by the ConfigurationManager.

  2. The path contains a prefix, we then assume that the path cans be interpreted by the ConfigurationManager.


Table 17.3. Descriptions of the fields of PortalContainerDefinition when it is used to define the default portal container

nameThe name of the portal container. This field is optional. The default portal name will be:
  1. If this field is not empty, then the default value will be the value of this field.

  2. If this field is empty and the value of the parameter default.portal.container is not empty, then the default value will be the value of the parameter.

  3. If this field and the parameter default.portal.container are both empty, the default value will be "portal".

restContextNameThe name of the context name of the rest web application. This field is optional. The default value wil be:
  1. If this field is not empty, then the default value will be the value of this field.

  2. f this field is empty and the value of the parameter default.rest.context is not empty, then the default value will be the value of the parameter.

  3. f this field and the parameter default.rest.context are both empty, the default value will be "rest".

realmNameThe name of the realm. This field is optional. The default value wil be:
  1. If this field is not empty, then the default value will be the value of this field.

  2. f this field is empty and the value of the parameter default.realm.name is not empty, then the default value will be the value of the parameter.

  3. f this field and the parameter default.realm.name are both empty, the default value will be "exo-domain".

dependenciesAll the dependencies of the portal container ordered by loading priority. This field is optional. If this field has a non empty value, it will be the default list of dependencies.
settingsA java.util.Map of internal parameters that we would like to tie the default portal container. Those parameters could have any type of value. This field is optional.
externalSettingsPathThe path of the external properties file to load as default settings to the default portal container. This field is optional. The external properties files can be either of type "properties" or of type "xml". The path will be interpreted as follows:
  1. The path doesn't contain any prefix of type "classpath:", "jar:" or "file:", we assume that the file could be externalized so we apply the following rules:

    1. A file exists at ${exo-conf-dir}/portal/${externalSettingsPath}, we will load this file.

    2. No file exists at the previous path, we then assume that the path cans be interpreted by the ConfigurationManager.

  2. The path contains a prefix, we then assume that the path cans be interpreted by the ConfigurationManager.


Internal and external settings are both optional, but if we give a non empty value for both the application will merge the settings. If the same setting name exists in both settings, we apply the following rules:

  1. The value of the external setting is null, we ignore the value.

  2. The value of the external setting is not null and the value of the internal setting is null, the final value will be the external setting value that is of type String.

  3. Both values are not null, we will have to convert the external setting value into the target type which is the type of the internal setting value, thanks to the static method valueOf(String), the following sub-rules are then applied:

    1. The method cannot be found, the final value will be the external setting value that is of type String.

    2. The method can be found and the external setting value is an empty String, we ignore the external setting value.

    3. The method can be found and the external setting value is not an empty String but the method call fails, we ignore the external setting value.

    4. The method can be found and the external setting value is not an empty String and the method call succeeds, the final value will be the external setting value that is of type of the internal setting value.

We can inject the value of the portal container settings into the portal container configuration files thanks to the variables which name start with "portal.container.", so to get the value of a setting called "foo" just use the following syntax ${portal.container.foo}. You can also use internal variables, such as:


You can find below an example of how to use the variables:

<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplaform.org/xml/ns/kernel_1_1.xsd http://www.exoplaform.org/xml/ns/kernel_1_1.xsd"
  xmlns="http://www.exoplaform.org/xml/ns/kernel_1_1.xsd">
  <component>
    <type>org.exoplatform.container.TestPortalContainer$MyComponent</type>
    <init-params>
      <!-- The name of the portal container -->
      <value-param>
        <name>portal</name>
        <value>${portal.container.name}</value>
      </value-param>
      <!-- The name of the rest ServletContext -->
      <value-param>
        <name>rest</name>
        <value>${portal.container.rest}</value>
      </value-param>
      <!-- The name of the realm -->
      <value-param>
        <name>realm</name>
        <value>${portal.container.realm}</value>
      </value-param>
      <value-param>
        <name>foo</name>
        <value>${portal.container.foo}</value>
      </value-param>
      <value-param>
        <name>before foo after</name>
        <value>before ${portal.container.foo} after</value>
      </value-param>
    </init-params>
  </component>
</configuration>

In the properties file corresponding to the external settings, you can reuse variables previously defined (in the external settings or in the internal settings) to create a new variable. In this case the prefix "portal.container." is not needed, see an example below:

my-var1=value 1
my-var2=value 2
complex-value=${my-var1}-${my-var2}

In the external and internal settings, you can also use create variables based on value of System paramaters. The System parameters can either be defined at launch time or thanks to the PropertyConfigurator (see next section for more details). See an example below:

temp-dir=${java.io.tmpdir}${file.separator}my-temp

However, for the internal settings you can use System parameters only to define settings of type java.lang.String.

It cans be also very usefull to define a generic variable in the settings of the default portal container, the value of this variable will change according to the current portal container. See below an example:

my-generic-var=value of the portal container "${name}"

If this variable is defined at the default portal container level, the value of this variable for a portal container called "foo" will be value of the portal container "foo".

A new property configurator service has been developed for taking care of configuring system properties from the inline kernel configuration or from specified property files.

The services is scoped at the root container level because it is used by all the services in the different portal containers in the application runtime.

The kernel configuration is able to handle configuration profiles at runtime (as opposed to packaging time).

Profiles are configured in the configuration files of the eXo kernel.

A configuration element is profiles capable when it carries a profiles element.

All applications on the top of eXo JCR that need a cache, can rely on an org.exoplatform.services.cache.ExoCache instance that is managed by the org.exoplatform.services.cache.CacheService. The main implementation of this service is org.exoplatform.services.cache.impl.CacheServiceImpl which depends on the org.exoplatform.services.cache.ExoCacheConfig in order to create new ExoCache instances. See below an example of org.exoplatform.services.cache.CacheService definition:

  <component>
    <key>org.exoplatform.services.cache.CacheService</key>
    <jmx-name>cache:type=CacheService</jmx-name>
    <type>org.exoplatform.services.cache.impl.CacheServiceImpl</type>
    <init-params>
      <object-param>
        <name>cache.config.default</name>
        <description>The default cache configuration</description>
        <object type="org.exoplatform.services.cache.ExoCacheConfig">
          <field name="name"><string>default</string></field>
          <field name="maxSize"><int>300</int></field>
          <field name="liveTime"><long>600</long></field>
          <field name="distributed"><boolean>false</boolean></field>
          <field name="implementation"><string>org.exoplatform.services.cache.concurrent.ConcurrentFIFOExoCache</string></field> 
        </object>
      </object-param>
    </init-params>
  </component>

See below an example of how to define a new ExoCacheConfig thanks to a external-component-plugin:

  <external-component-plugins>
    <target-component>org.exoplatform.services.cache.CacheService</target-component>
    <component-plugin>
      <name>addExoCacheConfig</name>
      <set-method>addExoCacheConfig</set-method>
      <type>org.exoplatform.services.cache.ExoCacheConfigPlugin</type>
      <description>Configures the cache for query service</description>
      <init-params>
        <object-param>
          <name>cache.config.wcm.composer</name>
          <description>The default cache configuration</description>
          <object type="org.exoplatform.services.cache.ExoCacheConfig">
            <field name="name"><string>wcm.composer</string></field>
            <field name="maxSize"><int>300</int></field>
            <field name="liveTime"><long>600</long></field>
            <field name="distributed"><boolean>false</boolean></field>
            <field name="implementation"><string>org.exoplatform.services.cache.concurrent.ConcurrentFIFOExoCache</string></field> 
          </object>
        </object-param>
      </init-params>
    </component-plugin>
  </external-component-plugins>

The factory for jboss cache, delegates the cache creation to ExoCacheCreator that is defines as below:

package org.exoplatform.services.cache.impl.jboss;
...
public interface ExoCacheCreator {

  /**
   * Creates an eXo cache according to the given configuration {@link org.exoplatform.services.cache.ExoCacheConfig}
   * @param config the configuration of the cache to apply
   * @param cache the cache to initialize
   * @exception ExoCacheInitException if an exception happens while initializing the cache
   */
  public ExoCache create(ExoCacheConfig config, Cache<Serializable, Object> cache) throws ExoCacheInitException;
  
  /**
   * Returns the type of {@link org.exoplatform.services.cache.ExoCacheConfig} expected by the creator  
   * @return the expected type
   */
  public Class<? extends ExoCacheConfig> getExpectedConfigType();
  
  /**
   * Returns the name of the implementation expected by the creator. This is mainly used to be backward compatible
   * @return the expected by the creator
   */
  public String getExpectedImplementation();
}

The ExoCacheCreator allows you to define any kind of jboss cache instance that you would like to have. It has been designed to give you the ability to have your own type of configuration and to always be backward compatible.

In an ExoCacheCreator, you need to implement 3 methods which are:

By default, no cache creator are defined, so you need to define them yourself by adding them in your configuration files.

You have 2 ways to define a cache which are:

Make sure you understand the Java Naming and Directory InterfaceTM (JNDI) concepts before using this service.

The InitialContextInitializer configuration example:

  <component>
    <type>org.exoplatform.services.naming.InitialContextInitializer</type>
    <init-params>
      <properties-param>
        <name>default-properties</name>
        <description>Default initial context properties</description>
        <property name="java.naming.factory.initial" value="org.exoplatform.services.naming.SimpleContextFactory"/>
      </properties-param>
      <properties-param>
        <name>mandatory-properties</name>
        <description>Mandatory initial context properties</description>
        <property name="java.naming.provider.url" value="rmi://localhost:9999"/>
      </properties-param>
    </init-params>
  </component>

The BindReferencePlugin component plugin configuration example (for JDBC datasource):

  <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>

InitialContextInitalizer also provides feature of references binding in runtime. References have bind in runtime will be persisted and automatically rebinded on a next system start. Java temp directory is used to persist references in bind-references.xml file.

Service provide methods for binding reference.

     public void bind(String bindName, 
                      String className, 
                      String factory, 
                      String factoryLocation, 
                      Map<String, String> refAddr) 
                 throws NamingException, FileNotFoundException, XMLStreamException;

Example of usage:

      // obtain InitialContextInitializer instance from ExoContainer (e.g. PortalContainer)
      InitialContextInitializer initContext = (InitialContextInitializer)container.getComponentInstanceOfType(InitialContextInitializer.class);
  
      Map<String, String> refAddr = new HashMap<String, String>();
      refAddr.put("driverClassName", "oracle.jdbc.OracleDriver");
      refAddr.put("url", "jdbc:oracle:thin:@oraclehost:1521:orcl");
      refAddr.put("username", "exouser");
      refAddr.put("password", "exopassword");

      initContext.bind("jdbcexco", "javax.sql.DataSource", "org.apache.commons.dbcp.BasicDataSourceFactory", null, refAddr);

      // try to get just bound DataSource
      DataSource ds = (DataSource)new InitialContext().lookup("jdbcexo");

Log4J is a very popular and flexible logging system. It is a good option for JBoss.

  <component>
    <type>org.exoplatform.services.log.LogConfigurationInitializer</type>
    <init-params>
      <value-param>
        <name>logger</name>
        <value>org.exoplatform.services.log.impl.BufferedLog4JLogger</value>
      </value-param>
      <value-param>
        <name>configurator</name>
        <value>org.exoplatform.services.log.impl.Log4JConfigurator</value>
      </value-param>
      <properties-param>
        <name>properties</name>
        <description>Log4J properties</description>
        <property name="log4j.rootLogger" value="DEBUG, stdout, file"/>
        <property name="log4j.appender.stdout" value="org.apache.log4j.ConsoleAppender"/>
        <property name="log4j.appender.stdout.layout" value="org.apache.log4j.PatternLayout"/>
        <property name="log4j.appender.stdout.layout.ConversionPattern" value="%d {dd.MM.yyyy HH:mm:ss} %c {1}: %m (%F, line %L) %n"/>
        <property name="log4j.appender.file" value="org.apache.log4j.FileAppender"/>
        <property name="log4j.appender.file.File" value="jcr.log"/>
        <property name="log4j.appender.file.layout" value="org.apache.log4j.PatternLayout"/>
        <property name="log4j.appender.file.layout.ConversionPattern" value="%d{dd.MM.yyyy HH:mm:ss} %m (%F, line %L) %n"/>
      </properties-param >
    </init-params>
  </component>

Service's configuration.

   <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="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost/" />
            <property name="username" value="root" />
            <property name="password" value="admin" />
         </properties-param>
         <properties-param>
            <name>db-creation</name>.
            <description>database creation properties</description>.
            <property name="scriptPath" value="script.sql" />
            <property name="username" value="testuser" />
            <property name="password" value="testpwd" />
         </properties-param>
      </init-params>
   </component>

db-connection properties section contains parameters needed for connection to database server

db-creation properties section contains paramaters for database creation using DDL script:

Specific db-connection properties section for different databases.

MySQL:

<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost/" />
<property name="username" value="root" />
<property name="password" value="admin" />

PostgreSQL:

<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://localhost/" />
<property name="username" value="root" />
<property name="password" value="admin" />

MSSQL:

<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
<property name="url" value="jdbc:sqlserver://localhost:1433;"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>

Sybase:

<property name="driverClassName" value="com.sybase.jdbc3.jdbc.SybDriver" />
<property name="url" value="jdbc:sybase:Tds:localhost:5000/"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>

Oracle:

<property name="driverClassName" value="oracle.jdbc.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@db2.exoua-int:1521:orclvm" />
<property name="username" value="root" />
<property name="password" value="admin" />

Table of Contents

24. eXo Web Services
24.1. eXo Web Services introduction
25. Introduction to the Representational State Transfer (REST)
25.1. Introduction
26. REST Service Tutorial
26.1. Introduction
26.2. Source code
26.3. Configuration
26.4. Build
26.5. Deploy
26.6. Usage
27. REST Migration to jsr311
27.1. REST service structure:
27.2. Migration Examples
27.3. Support for javax.ws.rs.core.Application
28. Groovy Scripts as REST Services
28.1. Overview
28.2. Loading script and save it in JCR
28.3. Instantiation
28.4. Deploy newly created Class as RESTful service
28.5. Script Lifecycle Management
28.6. Get node UUID example
28.7. Groovy script restrictions
29. REST Framework
29.1. Requirements
29.2. Implementation
29.2.1. ResourceContainer
29.2.2. Response
29.2.3. Transformer
29.2.4. Binding and unbinding components (ResourceContainers)
29.2.5. ResourceDispatcher
30. SOAP Service Tutorial
30.1. Introduction
30.2. Add JSR 181 support to the deployed application
30.2.1. Add dependencies
30.2.2. Configure CXF servlet
30.3. Write the Web Service class
30.4. Configure the component
30.5. Verify it works.
31. Central Authentication Service Configuration
31.1. Introduction
31.2. CAS configuration
31.2.1. Configure CAS server
31.2.2. Configure other tomcat instance
31.2.3. Usage
32. Kerberos SSO on Active Directory
32.1. Overview
32.2. Active Directory configuration
32.3. Setup webserver
33. oAuth
33.1. Overview
33.2. Provider
33.2.1. Configuration
33.2.2. Servlets
33.3. Consumer
33.3.1. How it works
33.3.2. web.xml
33.3.3. Stages
33.3.4. Token Alive Time
33.3.5. Token Cleaner Timeout
34. Cometd
34.1. definition
34.2. Use
34.3. Sample
34.4. improvements
35. Cometd cluster
35.1. Motivation
35.2. How it works
35.3. Bench
36. Framework for cross-domain AJAX
36.1. Motivation
36.2. Scheme (how it works)
36.3. A Working Sequence:
36.4. How to use it
37. Cometd Cluster Bench
37.1. How we test
37.2. Environment
37.3. Result
38. JavaScript WebDAV Library
38.1. Introduction to the JavaScript WebDAV library
38.2. Content of JavaScript WebDAV Library:
38.3. Example of using of JavaScript WebDAV library
38.4. How to get JavaScript WebDAV library
39. eXo WS 2.0 Release Notes (released on 2009.02.07)
39.1. Notable Changes
39.2. API changes
39.3. Dependencies changed
39.4. How to upgrade
39.5. System Requirements
39.6. Detailed Changelog
39.7. Download
40. eXo WS 2.0.1 Release Notes (released on 2009.04.16)
40.1. Notable Changes
40.2. API changes
40.3. Dependencies changed
40.4. How to upgrade
40.5. System Requirements
40.6. Detailed Changelog

Representational State Transfer (REST) is a style of software architecture for distributed hypermedia systems such as the World Wide Web. The term was introduced in the doctoral dissertation in 2000 by Roy Fielding, one of the principal authors of the Hypertext Transfer Protocol (HTTP) specification, and has come into widespread use in the networking community.

REST strictly refers to a collection of network architecture principles that outline how resources are defined and addressed. The term is often used in a looser sense to describe any simple interface that transmits domain-specific data over HTTP without an additional messaging layer such as SOAP or session tracking via HTTP cookies.

The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document or image, a temporal service (e.g. "today's weather in Los Angeles"), a collection of other resources, a non-virtual object (e.g. a person), and so on. In other words, any concept that might be the target of an author's hypertext reference must fit within the definition of a resource. A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time.

REST uses a resource identifier to identify the particular resource involved in an interaction between components. REST connectors provide a generic interface for accessing and manipulating the value set of a resource, regardless of how the membership function is defined or the type of software that is handling the request. URL or URN are the examples of a resource identifier.

REST components perform actions with a resource by using a representation to capture the current or intended state of that resource and transferring that representation between components. A representation is a sequence of bytes, plus representation metadata to describe those bytes. Other commonly used but less precise names for a representation include: document, file, and HTTP message entity, instance, or variant. A representation consists of data, metadata describing the data, and, on occasion, metadata to describe the metadata (usually for the purpose of verifying message integrity). Metadata are in the form of name-value pairs, where the name corresponds to a standard that defines the value's structure and semantics. The data format of a representation is known as a media type.


REST uses various connector types to encapsulate the activities of accessing resources and transferring resource representations. The connectors present an abstract interface for component communication, enhancing simplicity by providing a complete separation of concepts and hiding the underlying implementation of resources and communication mechanisms.


The primary connector types are client and server. The essential difference between the two is that a client initiates communication by making a request, whereas a server listens for connections and responds to requests in order to supply access to its services. A component may include both client and server connectors.

An important part of RESTful architecture is a well-defined interface to communicate, in particular it is a set of HTTP methods such as POST, GET, PUT and DELETE. These methods are often compared with the CREATE, READ, UPDATE, DELETE (CRUD) operations associated with database technologies. An analogy can also be made:

  • PUT is analogous to CREATE or PASTE OVER,

  • GET to READ or COPY,

  • POST to UPDATE or PASTE AFTER, and

  • DELETE to DELETE or CUT.

Note: RESTful architecture is not limited to those methods, one of good examples of extension is the WebDAV protocol.

The CRUD (Create, Read, Update and Delete) verbs are designed to operate with atomic data within the context of a database transaction. REST is designed around the atomic transfer of a more complex state and can be viewed as a mechanism for transferring structured information from one application to another.

HTTP separates the notions of a web server and a web browser. This allows the implementation of each to vary from the other based on the client/server principle. When used RESTfully, HTTP is stateless. Each message contains all the information necessary to understand the request.

As a result, neither the client nor the server needs to remember any communication-state between messages. Any state retained by the server must be modeled as a resource..

Important

This article describes REST framework before version exo-ws-2.0.

// ...
@URITemplate("/calculator/{item1}/{item2}/")
public class Calculator implements ResourceContainer {
}

Writing @URITemplate before the class definition gives you the possibility not to set it for each method. Furthermore the class must implement the interface ResourceContainer. This interface doesn't have any methods, it is just an indication for the ResourceBinder. Add the code for adding two integers.

// ...
@URITemplate("/calculator/{item1}/{item2}/")
public class Calculator implements ResourceContainer {
  @QueryTemplate("operation=add")
  @OutputTransformer(StringOutputTransformer.class)
  @HTTPMethod("GET")
  public Response add(@URIParam("item1") Integer item1,
      @URIParam("item2") Integer item2) {
    StringBuffer sb = new StringBuffer();
    sb.append(item1).append(" + ").append(item2).append(" = ").append(item1 + item2);
    return Response.Builder.ok(sb.toString(), "text/plain").build();
  }
}

@QueryTemplate("operation=add") - only requests with query parameters "operation=add" can reach this method; @OutputTransformer(StringOutputTransformer.class) - the output transformer; @HTTPMethod("GET") - the HTTP method "GET".

Write the code for other operations in the same way. Then the result should look like:

package org.exoplatform.services.rest.example;

import org.exoplatform.services.rest.HTTPMethod;
import org.exoplatform.services.rest.OutputTransformer;
import org.exoplatform.services.rest.QueryTemplate;
import org.exoplatform.services.rest.Response;
import org.exoplatform.services.rest.URIParam;
import org.exoplatform.services.rest.URITemplate;
import org.exoplatform.services.rest.container.ResourceContainer;
import org.exoplatform.services.rest.transformer.StringOutputTransformer;

@URITemplate("/calculator/{item1}/{item2}/")
@OutputTransformer(StringOutputTransformer.class)
public class Calculator implements ResourceContainer {
  
  @QueryTemplate("operation=add")
  @HTTPMethod("GET")
  public Response add(@URIParam("item1") Integer item1, @URIParam("item2") Integer item2) {
    StringBuffer sb = new StringBuffer();
    sb.append(item1).append(" + ").append(item2).append(" = ").append(item1 + item2);
    return Response.Builder.ok(sb.toString(), "text/plain").build();
  }

  @QueryTemplate("operation=subtract")
  @HTTPMethod("GET")
  public Response subtract(@URIParam("item1") Integer item1, @URIParam("item2") Integer item2) {
    StringBuffer sb = new StringBuffer();
    sb.append(item1).append(" - ").append(item2).append(" = ").append(item1 - item2);
    return Response.Builder.ok(sb.toString(), "text/plain").build();
  }

  @QueryTemplate("operation=multiply")
  @HTTPMethod("GET")
  public Response multiply(@URIParam("item1") Integer item1, @URIParam("item2") Integer item2) {
    StringBuffer sb = new StringBuffer();
    sb.append(item1).append(" * ").append(item2).append(" = ").append(item1 * item2);
    return Response.Builder.ok(sb.toString(), "text/plain").build();
  }

  @QueryTemplate("operation=divide")
  @HTTPMethod("GET")
  public Response divide(@URIParam("item1") Integer item1, @URIParam("item2") Integer item2) {
    StringBuffer sb = new StringBuffer();
    sb.append(item1).append(" / ").append(item2).append(" = ").append(item1 / item2);
    return Response.Builder.ok(sb.toString(), "text/plain").build();
  }
}

So we are done with the source code.

We have done all now. Then copy the jar file from the target directory of project exo-tomcat into the server with all prepared stuff for REST services. (You can download it here: http://forge.objectweb.org/project/download.php?group_id=151&file_id=9862)

So just put the jar file into the lib directory of the tomcat and restart it. In the console you should see this message:

[INFO] ResourceBinder - Bind new ResourceContainer: org.exoplatform.services.rest.example.Calculator@19846fd

This message indicates that our service was found, bound and is ready to work

Open your browser and type the following URL: http://localhost:8080/rest/calculator/12/5/?operation=add and you should see the next page:

The service is working. This is a very simple example, but it should help developers use the REST framework.

Try to check other URLs.

Important

The new implementation of the REST engine respects the jsr311 specification.

EXAMPLE #1

Old code

package org.exoplatform.services.rest.example;

import org.exoplatform.services.rest.HTTPMethod;
import org.exoplatform.services.rest.URITemplate;
import org.exoplatform.services.rest.URIParam;
import org.exoplatform.services.rest.container.ResourceContainer;
import org.exoplatform.services.rest.transformer.StringOutputTransformer;
import org.exoplatform.services.rest.OutputTransformer;
import org.exoplatform.services.rest.Response;

@URITemplate("/a/{1}/b")
public class Resource implements ResourceContainer {

  @HTTPMethod("GET")
  @URITemplate("{2}")
  @OutputTransformer(StringOutputTransformer.class)
  public Response m0(@URIParam("1") String param1, @URIParam("2") String param2) {
    Response resp = Response.Builder.ok(param1+param2, "text/plain").build();
    return resp;
  }
}

New code

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam

@Path("/a/{1}/b")
public class Resource implements ResourceContainer {
  @GET
  @Path("{2}")
  @Produces("text/plain")
  public String m0(@PathParam("1") String param1, @PathParam("2") String param2) {
    return path1+path2;
  }
}

EXAMPLE #2

Old code

import org.exoplatform.services.rest.HTTPMethod;
import org.exoplatform.services.rest.InputTransformer;
import org.exoplatform.services.rest.Response;
import org.exoplatform.services.rest.URITemplate;
import org.exoplatform.services.rest.container.ResourceContainer;
import org.exoplatform.ws.frameworks.json.transformer.Json2BeanInputTransformer;

public class Resource implements ResourceContainer {
  @HTTPMethod("POST")
  @URITemplate("/")
  @InputTransformer(Json2BeanInputTransformer.class)
  public Response m0(Book book) {
    // do something with bean
    return Response.Builder.noContent().build();  
  }
}

New code

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Consumes;

@Path("/")
public class Resource implements ResourceContainer {
  @POST
  @Consumes("application/json")
  public void m0(Book book) {
    // do something with bean
  }
}

There are two ways to save a script in JCR. The first way is to save it at server startup time by using configuration.xml and the second way is to upload the script via HTTP.

Load script at startup time

This way can be used for load prepared scripts, for use this way we must configure org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoaderPlugin. This is simple configuration example.

<external-component-plugins>
  <target-component>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoader</target-component>
  <component-plugin>
    <name>test</name>
    <set-method>addPlugin</set-method>
    <type>org.exoplatform.services.jcr.ext.script.groovy.GroovyScript2RestLoaderPlugin</type>
    <init-params>
      <value-param>
        <name>repository</name>
        <value>repository</value>
      </value-param>
      <value-param>
        <name>workspace</name>
        <value>production</value>
      </value-param>
      <value-param>
        <name>node</name>
        <value>/script/groovy</value>
      </value-param>
      <properties-param>
        <name>JcrGroovyTest.groovy</name>
        <property name="autoload" value="true" />
        <property name="path" value="file:/home/andrew/JcrGroovyTest.groovy" />
      </properties-param>
    </init-params>
  </component-plugin>
</external-component-plugins>

First value-param sets JCR repository, second value-param sets workspace and third one sets JCR node where scripts from plugin will be stored. If specified node does not exists then it will be created. List of scripts is set by properties-params. Name of each properties-param will be used as node name for stored script, property autoload says to deploy this script at startup time, property path sets the source of script to be loaded. In this example we try to load single script from local file /home/andrew/JcrGroovyTest.groovy.

Load script via HTTP

This is samples of HTTP requests. In this examples we will upload script from file with name test.groovy.

andrew@ossl:~> curl -u root:exo \
-X POST \
-H 'Content-type:script/groovy' \
--data-binary @test.groovy \
http://localhost:8080/rest/script/groovy/add/repository/production/script/groovy/test.groovy

This example imitate sending data with HTML form ('multipart/form-data'). Parameter autoload is optional. If parameter autoload=true then script will be instantiate and deploy script immediately.

andrew@ossl:~> curl -u root:exo \
-X POST \
-F "file=@test.groovy;name=test" \
-F "autoload=true" \
http://localhost:8080/rest/script/groovy/add/repository/production/script/groovy/test1.groovy

If GroovyScript2RestLoader configured as was decribed in previous section then all "autoload" scripts deployed. In first section we added script from file /home/andrew/JcrGroovyTest.groovy to JCR node /script/groovy/JcrGroovyTest.groovy, repository repository, workspace production. In section "Load script via HTTP" it was refered about load scripts via HTTP, there is an opportunity to manage the life cycle of script.

Undeploy script, which is alredy deployed:

andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/load/repository/production/script/groovy/JcrGroovyTest.groovy?state=false

Then deploy it again:

andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/load/repository/production/script/groovy/JcrGroovyTest.groovy?state=true

or even more simple:

andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/load/repository/production/script/groovy/JcrGroovyTest.groovy

Disable scripts autoloading, NOTE it does not change current state:

andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/repository/production/script/groovy/JcrGroovyTest.groovy/autoload?state=false

Enable it again:

andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/autoload/repository/production/script/groovy/JcrGroovyTest.groovy?state=true

and again more simpe variant:

andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/autoload/repository/production/script/groovy/JcrGroovyTest.groovy

Change script source code:

andrew@ossl:~> curl -u root:exo \
-X POST \
-H 'Content-type:script/groovy' \
--data-binary @JcrGroovyTest.groovy \
http://localhost:8080/rest/script/groovy/update/repository/production/script/groovy/JcrGroovyTest.groovy

This example imitate sending data with HTML form ('multipart/form-data').

andrew@ossl:~> curl -u root:exo \
-X POST \
-F "file=@JcrGroovyTest.groovy;name=test" \
http://localhost:8080/rest/script/groovy/update/repository/production/script/groovy/JcrGroovyTest.groovy

Remove script from JCR:

andrew@ossl:~> curl -u root:exo \
-X GET \
http://localhost:8080/rest/script/groovy/delete/repository/production/script/groovy/JcrGroovyTest.groovy

Now we are going to try simple example of Groovy RESTfull service. There is one limitation, even if we use groovy we should use Java style code and decline to use dynamic types, but of course we can use it in private methods and feilds. Create file JcrGroovyTest.groovy, in this example I save it in my home directory /home/andrew/JcrGroovyTest.groovy. Then configure GroovyScript2RestLoaderPlugin as described in section Load script at startup time.

import javax.jcr.Node
import javax.jcr.Session
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.PathParam
import org.exoplatform.services.jcr.RepositoryService
import org.exoplatform.services.jcr.ext.app.ThreadLocalSessionProviderService

@Path("groovy/test/{repository}/{workspace}")
public class JcrGroovyTest {
  private RepositoryService                 repositoryService
  private ThreadLocalSessionProviderService sessionProviderService
  
  public JcrGroovyTest(RepositoryService repositoryService,
                       ThreadLocalSessionProviderService sessionProviderService) {
    this.repositoryService = repositoryService
    this.sessionProviderService = sessionProviderService
  }
  

  @GET
  @Path("{path:.*}")
  public String nodeUUID(@PathParam("repository") String repository,
                         @PathParam("workspace") String workspace,
                         @PathParam("path") String path) {
    Session ses = null
    try {
      ses = sessionProviderService.getSessionProvider(null).getSession(workspace, repositoryService.getRepository(repository))
      Node node = (Node) ses.getItem("/" + path)
      return node.getUUID() + "\n"
    } finally {
      if (ses != null)
        ses.logout()
    }
  }

After configuration is done start server. If configuration is correct and script does not have syntax error you should see next:

In screenshot we can see service deployed.

First create folder via WebDAV in repository production, folder name 'test'. Now we can try access this service. Open another console and type command:

andrew@ossl:~> curl -u root:exo \
http://localhost:8080/rest/groovy/test/repository/production/test

Whe you try to execute this command you should have exception, because JCR node '/test' is not referenceable and has not UUID. We can try add mixin mix:referenceable. To do this add one more method in script. Open script from local source code /home/andrew/JcrGroovyTest.groovy, add following code and save file.

@POST
@Path("{path:.*}")
public void addReferenceableMixin(@PathParam("repository") String repository,
                                  @PathParam("workspace") String workspace,
                                  @PathParam("path") String path) {
  Session ses = null
  try {
    ses = sessionProviderService.getSessionProvider(null).getSession(workspace, repositoryService.getRepository(repository))
    Node node = (Node) ses.getItem("/" + path)
    node.addMixin("mix:referenceable")
    ses.save()
  } finally {
    if (ses != null)
      ses.logout()
  }
}

Now we can try to change script deployed on the server without server restart. Type in console next command:

andrew@ossl:~> curl -i -v -u root:exo \
-X POST \
--data-binary @JcrGroovyTest.groovy \
-H 'Content-type:script/groovy' \
http://localhost:8080/rest/script/groovy/update/repository/production/script/groovy/JcrGroovyTest.groovy

Node '/script/groovy/JcrGroovyTest.groovy' has property exo:autoload=true so script will be re-deployed automatically when script source code changed.

Script was redeployed, now try access newly created method.

andrew@ossl:~> curl -u root:exo \
-X POST \
http://localhost:8080/rest/groovy/test/repository/production/test

Method excution should be quiet, without output, traces, etc. Then we can try again get node UUID.

andrew@ossl:~> curl -u root:exo \
http://localhost:8080/rest/groovy/test/repository/production/test
1b8c88d37f0000020084433d3af4941f

Node UUID: 1b8c88d37f0000020084433d3af4941f

We don't need this scripts any more, so remove it from JCR.

andrew@ossl:~> curl -u root:exo \
http://localhost:8080/rest/script/groovy/delete/repository/production/script/groovy/JcrGroovyTest.groovy

Important

This describes REST framework before version exo-ws-2.0

In our REST architecture implementation, an HTTPServlet is the front-end for the REST engine. In this RESTful framework, endpoints for HTTP request are represented by java classes (ResourceContainers). All ResourceContainers are run as components of an ExoContainer, so they are configured within the same configuration file.

ResourceBinder and ResourceDispatcher are two other important components of the REST engine. ResourceBinder deals with binding and unbinding ResourceContainer. ResourceDispatcher dispatches REST requests to ResourceContainer. ResourceBinder and ResourceDispatcher are also components of the ExoContainer

A ResourceContainer is an annotated java class. Annotations must at least describe URLs and HTTP methods to be served by this container. But they may also describe other parameters, like produced content type or transformers. Transformers are special Java classes, used to serialize and deserialize Java objects.

A very simple ResourceContainer may look like this:

@HTTPMethod("GET")
@URITemplate("/test1/{param}/test2/")
public Response method1(@URIParam("param") String param) {
//...
//...
  Response resp = Response.Builder.ok().build();
  return resp;
}

The ResourceContainer described above is very simple, it can serve the GET HTTP method for the relative URL /test1/{param}/test2/ where {param} can be any string value. Additionally, {param} can be used as a method parameter of the container by annotating it as : @URIParam("param") String param. For example, in URL /test1/myID/test2 the method parameter "param" has the value "myID".

@URITemplate may be used for annotating classes or methods. If the TYPE scope annotation is for example @URITemplate("/testservices/") and a METHOD scope annotation is @URITemplate("/service1/") then that method can be called with the path /testservices/service1/. All ResourceContainer methods return Response objects. A Response includes HTTP status, an entity (the requested resource), HTTP headers and OutputEntityTransformer.

A client sends a request, after some operations, the HTTP request is parsed into three parts: the HTTP request stream, which consists of the body of the request, HTTP headers and query parameters. Then ResourceDispatcher gets this request. During the start of ExoContainers ResourceBinder collects all available ResourceContainers and after the start passes the list of ResourceContainers to the ResourceDispatcher. When ResourceDispatcher gets the request from a client it tries to search for a valid ResourceContainer. For this search ResourceDispatcher uses @HTTPMethod, @URITemplate and @ProducedMimeType annotations. The last one is not always necessary, if it is not specified this is set in / (all mime types). When ResourceDispatcher found the "right" ResourceContainer it tries to call a method with some parameters. ResourceDispatcher will send only parameters which ResourceContainer requests. In the code example above the parameter is a part of the requested URL between "/test1/" and "/test2/". ResourceContainer methods can consist only of some well-defined parameters. See the next rules:

All transformers can be created by EntityTransformerFactory. Transformers can be divided in two groups. Transformers from the first one extend the abstract class InputEntityTransformer. InputEntityTransformer implements the interface GenericInputEntityTransformer, and GenericInputEntityTransformer extends the interface GenericEntityTransformer. This architecture gives the possibility to use one class EntityTransformerFactory for creating both types of transformers (input and output). At first we will speak about InputEntityTransformer.

InputEntityTransformer

All classes which extend the abstract class InputEntitytransformer must override the method ObjectreadFrom(InputStream entityDataStream). This method must return an object with the same type as ResourceContainer requires in method parameters (one not annotated parameter). This mechanism works in the following way. For example, a class which extends InputEntityTransformer will be called SimpleInputTransformer. So, SimpleInputTransformer must have a simple constructor (without any parameters). SimpleInputTransformer also has two methods void setType(Class entityType) and Class getType(). Those methods are described in InputEntityTransformer. And, as we said above, SimpleInputTransformer must override the abstract method Object readFrom(InputStream entityDataStream). So, a developer needs to create a class which can build a Java Object from InputStream and then create annotation to ResourceContainer or some method in ResourceContainer. This annotation must define the type of InputEntityTransformer. Here is the code of the annotation InputTransformer.

//...
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface InputTransformer {
  Class<? extends InputEntityTransformer> value();
}

Then ResourceDispatcher gets InputTransformer from the factory during runtime then builds an Object from stream and adds it to the parameter array. See the code below.

//...
  InputEntityTransformer transformer = (InputEntityTransformer) factory_.newTransformer(resource.getInputTransformerType());
  transformer.setType(methodParameters[i]);
  params[i] = transformer.readFrom(request.getEntityStream());
//...
  Response response = (Response) resource.getServer().invoke(resource.getResourceContainer(), params);

A developer can find some prepared transformers in the package "org.exoplatform.services.rest.transformer".

OutputEntityTransformer

OutputEntityTransformer can be used to serialize the Object which represents the requested resource to OutputStream. OutputEntityTransformer, in future we will call it SimpleOutputTransformer, can be defined in a few ways.

Now some more words about transformation. The RESTful framework has two multi-purpose input/output transformers. One of them is JAXB(Input/Output)Transformer, and another one is Serializable(Input/Output)Transformer. The first one uses JAXB API for serializing and deserializing an object. Serializable (Input/Output)Transformer uses the own methods of an entity for serialization and deserialization. Here is an example:

/...
public class SimpleSerializableEntity implements SerializableEntity {

  String data;

  public SimpleSerializableEntity() {
  }
	
  public void readObject(InputStream in) throws IOException {
    StringBuffer sb = new StringBuffer();
    int rd = -1;
    while((rd = in.read()) != -1)
      sb.append((char)rd);
    data = sb.toString();
  }

    public void writeObject(OutputStream out) throws IOException {
    out.write(data.getBytes());
  }
}

SerializableInputTransformer will try to use the method void readObject(InputStream in) and SerializableOutputTransformer will try to use the method void writeObject(OutputStream in).

ResourceBinder takes care of binding and unbinding components which represent ResourceContainer. All ResourceContainers must be defined as ExoContainer components in configuration files, for example:

<component>
  <type>org.exoplatform.services.rest.samples.RESTSampleService</type>
</component>

ResourceBinder is an ExoContainer component and implements the interface org.picocontainer.Startable (see the picocontainer documentation). So at the start of ExoContainer ResourceBinder collects all available ResourceContainers. ResourceBinder makes validation for all ResourceContainers. At least each ResourceContainer must have the @HTTPMethod annotation and @URITemplate annotation. Other annotations are not obligatory. It's not allowed to have few ResourceContainers or methods with the same @HTTPMethod and @URITemplate annotation value. For example, if one container/method has the following code:

public class SimpleResourceContainer implements ResourceContainer {
  @HTTPMethod("GET")
  @URITemplate("/services/{id}/service1/")
  @InputTransformer(StringInputTransformer.class)
  @OutputTransformer(StringOutputTransformer.class)
  public Response method1(String data, @URIParam("id") String param) {
  // ... 
  }
}

than another component with @HTTPMethod("GET") and @URITemplate("/services/service1/{id}/") can't be bound. And now we'll try to explain this situation. On the one hand URITemplate is defined by value /services/{id}/service1/, instead of {id} there can be any other string value, so the combination /services/service1/service1/ is possible and valid. On the other hand another URITemplate /services/service1/{id}/ can have the string service1 instead of {id}, so for this resource the combination /services/service1/service1/ is possible and valid. And now we have two resources with the same URITemplate and the same HTTPMethod. This situation is not obligatory, we can't say what method we must call! In this case ResourceBinder generates InvalidResourceDescriptorException. Binder also checks the method parameters. In parameters only one parameter without annotation and of free type is allowed. All other parameters must have String type (or have a constructor with String) and be annotated. In other cases InvalidResourceDescriptorException* is generated. If all components have the "right" @HTTPMethod and @URITemplate annotations they should be bound without any problems. ResourceDispather gets a collection of ResourceContainers during the start and everything is ready to serve a request.

Note: within the scope of one component (one class) validation for URI templates is not implemented, so a developer must take care of @URITemplate and @HTTPMethod. Both of them must not be the same for different methods. Except if @QueryTemplate or/and @ProducedMimeTypes annotation is used.

Now let's talk about the features of ResourceDispatcher. ResourceDispatcher is the main part of the RESTful engine. Above we said some words about transformation and the role of ResourceDispatcher in this process. Now we are ready to talk about annotated method parameters. In one of the code examples above you could see the next construction

public Response method1(String data, @URIParam("id") String param) {

The method method1 is described with two parameters. The first parameter String data is built from the entity body (InputStream). The second one - String param is annotated by a special type Annotation. This Annotation has the following code:

@Target(value={PARAMETER})
@Retention(RUNTIME)
public @interface URIParam {
  String value();
}

How does it work? After having found the right method ResourceDispatcher gets the list of method parameters and starts handling them. Dispatcher tries to find the not annotated parameter (this entity body) and addresses InputEntityTransformer in order to build the required Object from InputStream. When dispatcher finds an annotated parameter it checks the type of annotation. It is possible to have four types of annotation: @URIParam, @HeaderParam, @QueryParam and @ContextParameter. If dispatcher has found the annotation @URIParam("id") then it takes the parameter with the key "id" from ResourceDescription and adds it into the parameter array. The same situation is with header, context and query parameters. So within the method a developer gets only the necessary parameters from header, query and uri. Some more information about @ContextParameters. @ContextParameters can be set in the configuration file of a component:

<component>
<type>org.exoplatform.services.rest.ResourceDispatcher</type>
  <init-params>
   <properties-param>
     <name>context-params</name>
     <property name="test" value="test_1"/>
   </properties-param>
  </init-params>
</component>

In this case any service can use this parameter, for example:

...method(@ContextParam("test") String p) {
  System.out.println(p);
}

After its execution you should see "test_1" in console.

And in each service the next context parameters can be used. The name of these parameters are described in ResourceDispatcher:

public static final String CONTEXT_PARAM_HOST = "host";
public static final String CONTEXT_PARAM_BASE_URI = "baseURI";
public static final String CONTEXT_PARAM_REL_URI = "relURI";
public static final String CONTEXT_PARAM_ABSLOCATION = "absLocation";

After that dispatcher finishes collecting parameters that the method requires, invokes this method and passes the result to the client as it is described above.

This part of code can explain how this mechanism works:

//...
      for (int i = 0; i < methodParametersAnnotations.length; i++) {
        if (methodParametersAnnotations[i] == null) {
          InputEntityTransformer transformer = (InputEntityTransformer) factory
              .newTransformer(resource.getInputTransformerType());
          transformer.setType(methodParameters[i]);
          params[i] = transformer.readFrom(request.getEntityStream());
        } else {
          Constructor<?> constructor = methodParameters[i].getConstructor(String.class);
          String constructorParam = null;
          Annotation a = methodParametersAnnotations[i];
          if (a.annotationType().isAssignableFrom(URIParam.class)) {
            URIParam u = (URIParam) a;
            constructorParam = request.getResourceIdentifier().getParameters().get(u.value());
          } else if (a.annotationType().isAssignableFrom(HeaderParam.class)) {
            HeaderParam h = (HeaderParam) a;
            constructorParam = request.getHeaderParams().get(h.value());
          } else if (a.annotationType().isAssignableFrom(QueryParam.class)) {
            QueryParam q = (QueryParam) a;
            constructorParam = request.getQueryParams().get(q.value());
          } else if (a.annotationType().isAssignableFrom(ContextParam.class)) {
            ContextParam c = (ContextParam) a;
            constructorParam = contextHolder_.get().get(c.value());
          }
          if (methodParameters[i].isAssignableFrom(String.class)) {
            params[i] = constructorParam;
          } else {
            params[i] = (constructorParam != null) ? constructor.newInstance(constructorParam) : null;
          }
        }
      }
      Response resp = (Response) resource.getServer().invoke(resource.getResourceContainer(), params);
//...

The more detailed schema looks like this:

We have a tiny sample to demonstrate called : TicketOrderService.

Following JSR 181, it is now just a matter of annotating a class to create a Web Service. The main idea is the your interface has to extends extends AbstractSingletonWebService.

@WebService
public interface TicketOrderService extends AbstractSingletonWebService {

  /**
   * @param departing departing place.
   * @param arriving arriving place.
   * @param departureDate departure date.
   * @param passenger passenger.
   * @return ticket order.
   */
  public String getTicket(String departing, String arriving, Date departureDate, String passenger);

  /**
   * @param confirmation confirm or not.
   */
  public void confirmation(boolean confirmation);

}
@WebService(serviceName = "TicketOrderService", 
            portName = "TicketOrderServicePort", 
            targetNamespace = "http://exoplatform.org/soap/cxf")
public class TicketOrderServiceImpl implements TicketOrderService {

  /**
   * Logger.
   */
  private static final Log LOG = ExoLogger.getLogger(TicketOrderServiceImpl.class);

  /**
   * Ticket.
   */
  private Ticket           ticket;

  /**
   * @param departing departing place.
   * @param arriving arriving place.
   * @param departureDate departure date.
   * @param passenger passenger.
   * @return ticket order.
   */
  public String getTicket(String departing, String arriving, Date departureDate, String passenger) {
    ticket = new Ticket(passenger, departing, arriving, departureDate);
    LOG.info(ticket);
    return String.valueOf(ticket.getOrder());
  }

  /**
   * @param confirmation confirm or not.
   */
  public void confirmation(boolean confirmation) {
    LOG.info("Confirmation : " + confirmation + " for order '" + ticket.getOrder() + "'.");
  }

}

To test quickly, you can simply grab the jar on eXo repository and deploy within your application.

This article try to explain how to configure CAS server and client for exo. For this example we will use 2 the same tomcat instance, but one for it has additation CAS server.

This configuration is not very useful, but very good example for configuration CAS.

Tomcat 1 deployed on windows 2003 - this is CAS server, tomcat 2 on Ubuntu 7.10.

(DNS name: test01-srv.exoua-int)

1.Certificates.

E:/Program Files>cd java
E:/Program Files/Java>cd jre1.5.0_11
E:/Program Files/Java/jre1.5.0_11>cd bin
E:/Program Files/Java/jre1.5.0_11/bin>keytool -genkey -alias tomcat -keypass 123456 -keyalg RSA
Enter keystore password:  123456
What is your first and last name?
  [Unknown]:  test01-srv.exoua-int
What is the name of your organizational unit?
  [Unknown]:  .
What is the name of your organization?
  [Unknown]:  .
What is the name of your City or Locality?
  [Unknown]:  .
What is the name of your State or Province?
  [Unknown]:  .
What is the two-letter country code for this unit?
  [Unknown]:  UA
Is CN=test01-srv.exoua-int, OU=., O=., L=., ST=., C=UA correct?
  [no]:  yes

E:/Program Files/Java/jre1.5.0_11/bin>keytool -export -alias tomcat -keypass 123456 -file server.crt
Enter keystore password: 123456
Certificate stored in file <server.crt>

This is optional, I just want to have the same password for storage %JRE_HOME%/lib/security/cacerts.

E:/Program Files/Java/jre1.5.0_11/bin>keytool -storepasswd -keystore ../lib/security/cacerts
Enter keystore password: changeit
New keystore password: 123456
Re-enter new keystore password: 123456

Continue with certificates.

E:/Program Files/Java/jre1.5.0_11/bin>keytool -import -file server.crt -keypass 123456 -keystore ../lib/security/cacerts
Enter keystore password: 123456
Owner: CN=test01-srv.exoua-int, OU=., O=., L=., ST=., C=UA
Issuer: CN=test01-srv.exoua-int, OU=., O=., L=., ST=., C=UA
Serial number: 4810c6c5
Valid from: Fri Apr 24 20:33:36 HST 2008 until: Thu Jul 23 20:33:36 HST 2008
Certificate fingerprints:
MD5: CC:3B:FB:FB:AE:12:AD:FB:3E:D 5:98:CB:2E:3B:0A:AD
SHA1: A1:16:80:68:39:C7:58:EA:2F:48:59:AA:1D:73:5F:56:78:CE:A4:CE
Trust this certificate? [no]: yes
Certificate was added to keystore

E:/Program Files/Java/jre1.5.0_11/bin>

2. Now edit server.xml file for tomcat (we are using 6.0.13 everywhere).Uncomment configuration for SSL connection end edit it, it must looks as this:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="E:/Documents and Settings/admin/.keystore"
keystorePass="123456"
keyAlias="tomcat"
truststoreFile="E:/Program Files/Java/jre1.5.0_11/lib/security/cacerts"
truststorePass="123456" />

3. Now configure client part of CAS, as example we will use portal/private/* .Edit file /portal/WEB-INF/web.xml.

<context-param>
  <param-name>serverName</param-name>
  <param-value>http://test01-srv.exoua-int:8080</param-value>
</context-param>

Configure client, in this example we will protect /portal/private/* resource. Note: These filter must be add before this filter "SetCurrentIdentityFilter", the same think for filter-mapping.

<filter>
  <filter-name>SingleSignOutFilter</filter-name>
  <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter>
  <filter-name>AuthenticationFilter</filter-name>
  <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
  <init-param>
    <param-name>casServerLoginUrl</param-name>
    <param-value>https://test01-srv.exoua-int:8443/cas/login</param-value>
  </init-param>
</filter>
<filter>
  <filter-name>Cas20ProxyReceivingTicketValidationFilter</filter-name>
  <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
  <init-param>
    <param-name>casServerUrlPrefix</param-name>
    <param-value>https://test01-srv.exoua-int:8443/cas</param-value>
  </init-param>
  <init-param>
    <param-name>redirectAfterValidation</param-name>
    <param-value>true</param-value>
  </init-param>
</filter>
<filter>
  <filter-name>HttpServletRequestWrapperFilter</filter-name>
  <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<!-- eXo -->
<filter>
  <filter-name>BaseIdentityInitializerFilterImpl</filter-name>
  <filter-class>org.exoplatform.services.security.cas.client.impl.BaseIdentityInitializerFilterImpl</filter-class>
</filter>
<!-- end exo -->
....
<filter-mapping>
  <filter-name>SingleSignOutFilter</filter-name>
  <url-pattern>/private/*</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>AuthenticationFilter</filter-name>
  <url-pattern>/private/*</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>Cas20ProxyReceivingTicketValidationFilter</filter-name>
  <url-pattern>/private/*</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>HttpServletRequestWrapperFilter</filter-name>
  <url-pattern>/private/*</url-pattern>
</filter-mapping>
<!-- exo -->
<filter-mapping>
  <filter-name>BaseIdentityInitializerFilterImpl</filter-name>
  <url-pattern>/private/*</url-pattern>
</filter-mapping>
<!-- end exo -->
....
<listener>
  <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
....
<!-- exo -->
<servlet>
  <servlet-name>LogoutServlet</servlet-name>
  <servlet-class>org.exoplatform.services.security.cas.client.impl.LogoutServlet</servlet-class>
  <init-param>
    <param-name>casServerLogoutUrl</param-name>
    <param-value>https://test01-srv.exoua-int:8443/cas/logout</param-value>
  </init-param>
  <init-param>
    <param-name>redirectToUrl</param-name>
    <param-value>http://test01-srv.exoua-int:8080/portal/public/classic</param-value>
  </init-param>
</servlet>
<!-- end exo -->
.....
<!-- exo -->
<servlet-mapping>
  <servlet-name>LogoutServlet</servlet-name>
  <url-pattern>/logout/*</url-pattern>
</servlet-mapping>
<!-- end exo -->
....

4. Download and build code from http://svn.exoplatform.org/svnroot/exoplatform/projects/ws/trunk/security/cas/client

5. Download and put cas-client-core-3.1.1.jar in CATALINA_HOME%/lib directory.

6. Download CAS server source code and build it or download binary. Put cas.war in webapps directoryChange configuration in file /cas/WEB-INF/deployConfigContext.xml. Comment test authenticator and add new one.

<!--
<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />
-->
<!-- will check username and password at remote host -->
<bean class="org.exoplatform.services.security.cas.server.HTTPAuthenticationHandler"
p:authenticationURL="https://ubu.exoua-int:8443/portal/login" />

7. Download and build code which can do remote authentication.Download and build code from http://svn.exoplatform.org/svnroot/exoplatform/projects/ws/trunk/security/cas/server, andput it cas/WEB-INF/lib directory. This CAS server side handler which can call remote eXo authenticatiob service via HTTP.In this case validation of username/password wiil be done at ubu.exoua-int, but authentication sever (CAS) will be at test01-srv.exoua-int

Configure other tomcat instance, deploy it on Ubuntu 7.10 (DNS name: ubu.exoua-int).

1. Generate certificates for CAS client. The same as for previous but change name to ubu.exoua-int.

2. Edit server.xml file for tomcat.

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="/home/andrew/.keystore"
keystorePass="123456"
keyAlias="tomcat"
truststoreFile="/home/andrew/lib/java/jre/lib/security/cacerts"
truststorePass="123456" />

3. Edit file portal/WEB-INF/web.xml, add next strings in it. Change context parameter.

<context-param>
  <param-name>serverName</param-name>
  <param-value>http://ubu.exoua-int:8080</param-value>
</context-param>

Filters configuration must be the same as in client part on tomcat1. But add one more servlet which will check username/password.

<filter>
  <filter-name>SingleSignOutFilter</filter-name>
  <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter>
  <filter-name>AuthenticationFilter</filter-name>
  <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
  <init-param>
    <param-name>casServerLoginUrl</param-name>
    <param-value>https://test01-srv.exoua-int:8443/cas/login</param-value>
  </init-param>
</filter>
<filter>
  <filter-name>Cas20ProxyReceivingTicketValidationFilter</filter-name>
  <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
  <init-param>
    <param-name>casServerUrlPrefix</param-name>
    <param-value>https://test01-srv.exoua-int:8443/cas</param-value>
  </init-param>
  <init-param>
    <param-name>redirectAfterValidation</param-name>
    <param-value>true</param-value>
  </init-param>
</filter>
<filter>
  <filter-name>HttpServletRequestWrapperFilter</filter-name>
  <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<!-- exo -->
<filter>
  <filter-name>BaseIdentityInitializerFilterImpl</filter-name>
  <filter-class>org.exoplatform.services.security.cas.client.impl.BaseIdentityInitializerFilterImpl\
</filter-class>
</filter>
<!-- end exo -->
....
<filter-mapping>
  <filter-name>SingleSignOutFilter</filter-name>
  <url-pattern>/private/&#42;</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>AuthenticationFilter</filter-name>
  <url-pattern>/private/&#42;</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>Cas20ProxyReceivingTicketValidationFilter</filter-name>
  <url-pattern>/private/&#42;</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>HttpServletRequestWrapperFilter</filter-name>
  <url-pattern>/private/&#42;</url-pattern>
</filter-mapping>
<!-- exo -->
<filter-mapping>
  <filter-name>BaseIdentityInitializerFilterImpl</filter-name>
  <url-pattern>/private/&#42;</url-pattern>
</filter-mapping>
<!-- end exo -->
....
<listener>
  <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
....
<!-- exo -->
<servlet>
  <servlet-name>BaseHTTPUsernamePasswordValidator</servlet-name>
  <servlet-class>org.exoplatform.services.security.cas.client.impl.BaseHTTPUsernamePasswordValidatorImpl</servlet-class>
</servlet>
<servlet>
  <servlet-name>LogoutServlet</servlet-name>
  <servlet-class>org.exoplatform.services.security.cas.client.impl.LogoutServlet</servlet-class>
  <init-param>
    <param-name>casServerLogoutUrl</param-name>
    <param-value>https://test01-srv.exoua-int:8443/cas/logout</param-value>
  </init-param>
  <init-param>
    <param-name>redirectToUrl</param-name>
    <param-value>http://ubu.exoua-int:8080/portal/public/classic</param-value>
  </init-param>
</servlet>
<!-- end exo -->
.....
<!-- exo -->
<servlet-mapping>
  <servlet-name>BaseHTTPUsernamePasswordValidator</servlet-name>
  <url-pattern>/login/&#42;</url-pattern>
</servlet-mapping>
<servlet-mapping>
  <servlet-name>LogoutServlet</servlet-name>
  <url-pattern>/logout/&#42;</url-pattern>
</servlet-mapping>
<!-- end exo -->
<!-- not use default authentification-->
<!--
<security-constraint>
    <web-resource-collection>
      <web-resource-name>user authentication</web-resource-name>
      <url-pattern>/private/&#42;</url-pattern>
      <http-method>POST</http-method>
      <http-method>GET</http-method>
    </web-resource-collection>
    <auth-constraint>
      <role-name>users</role-name>
    </auth-constraint>
    <user-data-constraint>
      <transport-guarantee>NONE</transport-guarantee>
    </user-data-constraint>
  </security-constraint>

  <login-config>
    <auth-method>FORM</auth-method>
    <realm-name>exo-domain</realm-name>
    <form-login-config>
      <form-login-page>/login/jsp/login.jsp</form-login-page>
      <form-error-page>/login/jsp/login.jsp</form-error-page>
    </form-login-config>
  </login-config>

  <security-role>
    <description>a simple user role</description>
    <role-name>users</role-name>
  </security-role>

  <security-role>
    <description>the admin role</description>
    <role-name>admin</role-name>
  </security-role>
-->
<!-- end web.xml file-->
....

4. Download and build code from http://svn.exoplatform.org/svnroot/exoplatform/projects/ws/trunk/security/cas/client

5. Download and put cas-client-core-3.1.1.jar in CATALINA_HOME%/lib directory.

6. Now get trusted certificate for CAS server instance. To do this download and compile this file: http://blogs.sun.com/andreas/resource/InstallCert.java Then run it:

java InstallCert test01-srv.exoua-int:8443 123456

Change 123456 to actual password for keystore. You can see some exception but finally you must see info about certificates and prompt about adding it in storage. Select certificate, usually type 1 end press Enter.

Finish!!!

Run both servers, and try open one of protected URLs, for example http://test01-srv.exoua-int:8080/portal/private/classic. Accept certificates, and in login page username/password: root/exo. You must get private area in portal as root, then open other protected resource on server 'ubu.exoua-int', http://ubu.exoua-int:8080/portal/private/classic. And you must get this private area in other portal without login. If you get it then SSO for login work as well.

No try logout on 'ubu.exoua-int'. To do it directly from portal one groovy script must be modified, but it is not described here.

After logout from 'ubu.exoua-int' you should be asked about login at 'test01-srv.exoua-int'. The SSO for logout work well also.

That is all!

If it works as described above, then configuration right and SSO works.

On the AD server, we need to create a Kerberos identification for Tomcat Server :

1. Create a user account for the host computer on which Tomcat Server runs in the Active Directory server. (Select New > User, not New > Machine.)

When creating the two user accounts, use the simple name of the computer, and I recommend give names as next pattern host_host-name and http_host-name. First account will be used for LDAP connection, second one will be used for authentication service via HTTP. For example, if the host name is ubu.exoua-int, create a users in Active Directory called host_ubu and http_ubu.

Note the password you defined when creating the user account. You will need it in step 3. Do not select the "User must change password at next login" option, or any other password options.

2. Configure the new user account to comply with the Kerberos protocol.

3. Generate keys for service.

C:\> ktpass -princ host/ubu.exoua-int@EXOUA-INT -mapuser host_ubu@EXOUA-INT -crypto RC4-HMAC-NT \
-ptype KRB5_NT_PRINCIPAL -mapop set -pass 123456 -out c:\host_ubu.keytab


C:\> ktpass -princ HTTP/ubu.exoua-int@EXOUA-INT -mapuser http_ubu@EXOUA-INT -crypto RC4-HMAC-NT \
-ptype KRB5_NT_PRINCIPAL -mapop set -pass 123456 -out c:\http_ubu.keytab

4. Use the setspn utility to create the Service Principal Names (SPNs) for the user account created in step 1. Enter the following commands:

C:\> setspn -A host/ubu.exoua-int host_ubu
C:\> setspn -A HTTP/ubu.exoua-int http_ubu

5. Check which SPNs are associated with your user account, using the following command:

C:\> setspn -L host_ubu

NOTE This is an important step. If the same service is linked to a different account in the Active Directory server, the client will not send a Kerberos ticket to the server. If filter will be used secure-constraint must be removed from web.xml

6. Configuration on Linux host. This is example of file /etc/krb5.conf

[logging]                                                                                                                                                                            
 default = FILE:/var/log/krb5libs.log                                                                                                                                                
 kdc = FILE:/var/log/krb5kdc.log                                                                                                                                                     
 admin_server = FILE:/var/log/kadmind.log                                                                                                                                            
                                                                                                                                                                                     
[libdefaults]                                                                                                                                                                        
  ticket_lifetime = 24000                                                                                                                                                            
  default_realm = EXOUA-INT                                                                                                                                                          
  default_tkt_enctypes = rc4-hmac                                                                                                                                                    
  default_tgs_enctypes = rc4-hmac                                                                                                                                                    
                                                                                                                                                                                     
[realms]                                                                                                                                                                             
  EXOUA-INT = {                                                                                                                                                                      
    kdc = test01-srv.exoua-int:88                                                                                                                                                    
    admin_server = test01-srv.exoua-int:749                                                                                                                                          
    default_domain = EXOUA-INT                                                                                                                                                       
  }                                                                                                                                                                                  
                                                                                                                                                                                     
[domain_real]                                                                                                                                                                        
  .exoua-int = EXOUA-INT                                                                                                                                                             
  exoua-int = EXOUA-INT                                                                                                                                                              
                                                                                                                                                                                     
[kdc]                                                                                                                                                                                
  profile = /etc/kdc.conf                                                                                                                                                            
                                                                                                                                                                                     
[pam]                                                                                                                                                                                
  debug = false                                                                                                                                                                      
  ticket_lifetime = 36000                                                                                                                                                            
  renew_lifetime = 36000                                                                                                                                                             
  forwardable = true                                                                                                                                                                 
  krb4_convert = false

7. Copy key generated on step 3 to the Linux machine where tomcat server runs.

8. Run the ktutil utility on the Linux machine and import keys.

andrew@ubu:~$ ktutil
ktutil: rkt host_ubu.keytab
ktutil: wkt host.keytab
ktutil: rkt http_ubu.keytab
ktutil: wkt http.keytab

You must get to new files with tickets.

1. Deploy an exo-tomcat, and copy the jar for SSO in lib folder and change configuration.xml file to your network settings :

<configuration>                                                                                                                     
  <component>                                                                                                                       
    <key>org.exoplatform.services.security.sso.config.SSOConfigurator</key>                                                         
    <type>org.exoplatform.services.security.sso.config.SSOConfigurator</type>                                                       
    <init-params>                                                                                                                   
      <properties-param>                                                                                                            
        <name>sso-properties</name>                                                                                                 
        <property name="charset" value="UnicodeLittleUnmarked" />                                                                   
        <property name="domain" value="EXOUA-INT" />                                                                                
        <property name="jaas-context" value="krb5.ldap-action" />                                                                   
        <property name="ldap-server" value="ldap://test01-srv.exoua-int:389/" />                                                    
        <!--                                                                                                                        
        **********************************************************                                                                  
        Default cross domain authentication is disabled.                                                                            
        NOTE: This is actual for NTLM only.                                                                                         
        For SPNEGO cross domain authentication is disabled by default.                                                              
        There is some more work to enable it for SPNEGO.                                                                            
        **********************************************************                                                                  
        -->                                                                                                                         
        <!--                                                                                                                        
        <property name="cross-domain" value="true" />                                                                               
        -->                                                                                                                         
        <!--                                                                                                                        
        <property name="redirect-on-error" value="http://google.com" />                                                             
        -->                                                                                                                         
      </properties-param>                                                                                                           
    </init-params>                                                                                                                  
  </component>

2. In exo-tomcat/conf/Catalina/localhost/, change a file portal.xml :

<Context path='/portal' docBase='portal' debug='0' reloadable='true' crossContext='true'>                                           
  <Logger className='org.apache.catalina.logger.SystemOutLogger'                                                                    
          prefix='localhost_portal_log.' suffix='.txt' timestamp='true'/>                                                           
  <Manager className='org.apache.catalina.session.PersistentManager' saveOnRestart='false'/>                                        
  <!--                                                                                                                              
  <Realm className='org.apache.catalina.realm.JAASRealm'                                                                            
         appName='exo-domain'                                                                                                       
         userClassNames='org.exoplatform.services.security.jaas.UserPrincipal'                                                      
         roleClassNames='org.exoplatform.services.security.jaas.RolePrincipal'                                                      
         debug='0' cache='false'/>                                                                                                  
         <Valve className='org.apache.catalina.authenticator.FormAuthenticator' characterEncoding='UTF-8'/>                         
  -->                                                                                                                        
   <Valve className="org.exoplatform.services.security.sso.tomcat.SSOAuthenticatorValve"/>                                    
</Context>

Secure configuration in web.xml must be changed to next:

<security-role>
    <description>a simple user role</description>
    <role-name>users</role-name>
  </security-role>

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>portal</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>users</role-name>
    </auth-constraint>

    <user-data-constraint>
      <transport-guarantee>NONE</transport-guarantee>
    </user-data-constraint>
  </security-constraint>

NOTE At list one roles in web.xml must be corresponding to user group in AD.

3. Download in lib folder jcif-1.2.17.jar . We need this for support NTLM authentication.

4. In exo-tomcat/conf/jaas.conf, add :

com.sun.security.jgss.accept {                                                                                                      
  com.sun.security.auth.module.Krb5LoginModule required                                                                             
  keyTab = "/home/andrew/http.keytab"                                                                                               
  useKeyTab = true                                                                                                                  
  storeKey = true                                                                                                                   
  principal = "HTTP/ubu.exoua-int@EXOUA-INT"                                                                                        
  doNotPrompt = true                                                                                                                
  realm = "EXOUA-INT"                                                                                                               
  refreshKrb5Config = true                                                                                                          
  debug = false                                                                                                                     
  ;                                                                                                                                 
};                                                                                                                                  
                                                                                                                                    
krb5.ldap-action {                                                                                                                  
  com.sun.security.auth.module.Krb5LoginModule required                                                                             
  keyTab = "/home/andrew/host.keytab"                                                                                               
  useKeyTab = true                                                                                                                  
  storeKey = true                                                                                                                   
  principal = "host/ubu.exoua-int@EXOUA-INT"                                                                                        
  doNotPrompt = true                                                                                                                
  realm = "EXOUA-INT"                                                                                                               
  refreshKrb5Config = true                                                                                                          
  debug = false                                                                                                                     
  ;                                                                                                                                 
};

5. Add next system properties in file bin/eXo.sh

KERBEROS="-Djavax.security.auth.useSubjectCredsOnly=false \
-Djava.security.krb5.kdc=test01-srv.exoua-int \
-Djava.security.krb5.realm=EXOUA-INT"

JAVA_OPTS="$YOURKIT_PROFILE_OPTION $JAVA_OPTS $LOG_OPTS $SECURITY_OPTS $EXO_OPTS $EXO_CONFIG_OPTS $KERBEROS"

6. For portal add one more filter for initialize Identity for user org.exoplatform.services.security.sso.http.JndiIdentityInitalizerFilter this filter must be mapped before org.exoplatform.services.security.web.SetCurrentIdentityFilter on private area.

7. For Firefox, there is an additionnal configuration to do to use AD authentication : in adress bar, go to about:config. Filter on ntlm and choose "network.automatic-ntlm-auth.trusted-uris". Set string to the name of the machine where webserver run (in this exemple : set "ubu"). For IE, there is no additional configuration.

8. Go to http://ubu.exoua-int:8080/portal/private/classic If use was login windows (domain authentication) then, Active directory authentication will be used.

Instead tomcat valve org.exoplatform.services.security.sso.tomcat.SSOAuthenticatorValve can be used filter org.exoplatform.services.security.sso.http.SSOAuthenticationFilter

9. Important about using NTLM. JCIF may use MAC for signature connection to DC. In this case when one user logined, then next user may be not able login during time specified in property jcifs.smb.client.soTimeout (in ms), default 15000. This time JCIF keeps previous connection opened and may not create new one to authenticate other user. Must be set next properties to fix this jcifs.smb.client.domain, jcifs.smb.client.username, jcifs.smb.client.password. For example

-Djcifs.smb.client.domain=EXOUA-INT -Djcifs.smb.client.username=Admin -Djcifs.smb.client.password=secret"

In this case SMB connection will be signed for user.

The provider consists of two parts oauthprovider.war and exo.ws.security.oauth.provider.service-trunk.jar. The main part of the provider is OAuthProviderService, currently there is one implementation of this interface org.exoplatform.ws.security.oauth.impl.OAuthProviderServiceMD5Impl; this component has few required configuration parameters.

The web part of the provider consists of 3 servlets:

<?xml version="1.0" encoding="UTF-8"?>                                                                                                                                                                         
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee                                       
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">                                                                                                                                                      
  <display-name>oAuth provider</display-name>                                                                                                                                                                  
  <context-param>                                                                                                                                                                                              
    <description>Login page name</description>                                                                                                                                                                 
    <param-name>login-page</param-name>                                                                                                                                                                        
    <param-value>login/jsp/login.jsp</param-value>                                                                                                                                                             
  </context-param>                                                                                                                                                                                             
  <servlet>                                                                                                                                                                                                    
    <servlet-name>OAuthAuthenticationServlet</servlet-name>                                                                                                                                                    
    <servlet-class>org.exoplatform.ws.security.oauth.http.OAuthAuthenticationServlet</servlet-class>                                                                                                           
  </servlet>                                                                                                                                                                                                   
  <servlet>                                                                                                                                                                                                    
    <servlet-name>OAuthRequestTokenServlet</servlet-name>                                                                                                                                                      
    <servlet-class>org.exoplatform.ws.security.oauth.http.OAuthRequestTokenServlet</servlet-class>                                                                                                             
  </servlet>                                                                                                                                                                                                   
  <servlet>                                                                                                                                                                                                    
    <servlet-name>OAuthAccessTokenServlet</servlet-name>                                                                                                                                                       
    <servlet-class>org.exoplatform.ws.security.oauth.http.OAuthAccessTokenServlet</servlet-class>                                                                                                              
  </servlet>                                                                                                                                                                                                   
  <servlet-mapping>                                                                                                                                                                                            
    <servlet-name>OAuthAuthenticationServlet</servlet-name>                                                                                                                                                    
    <url-pattern>/authorize/*</url-pattern>                                                                                                                                                                    
  </servlet-mapping>                                                                                                                                                                                           
  <servlet-mapping>                                                                                                                                                                                            
    <servlet-name>OAuthRequestTokenServlet</servlet-name>                                                                                                                                                      
    <url-pattern>/request_token/*</url-pattern>                                                                                                                                                                
  </servlet-mapping>                                                                                                                                                                                           
  <servlet-mapping>                                                                                                                                                                                            
    <servlet-name>OAuthAccessTokenServlet</servlet-name>                                                                                                                                                       
    <url-pattern>/access_token/*</url-pattern>                                                                                                                                                                 
  </servlet-mapping>                                                                                                                                                                                           
</web-app>

The consumer consists of OAuthConsumerService and web part (servlets and filters).

OAuthConsumerFilter checks cookies in client's request. The cookie must have the name _consumer_name_.oauth_token and _consumer_name_.oauth_token_secret. Then this filter try to find the request/access token this at OAuthConsumerService. If the token from the request is an access token then the client is already authenticated and gets access to requested resource.

Otherwise the client will be redirected to the provider for authentication (see below, property "provider.authorizationURL"). This is the part of configuration.xml for the consumer:

<component>                                                                                                                                                                                                  
  <type>org.exoplatform.ws.security.oauth.impl.OAuthConsumerServiceImpl</type>                                                                                                                               
  <init-params>                                                                                                                                                                                              
    <value-param>                                                                                                                                                                                            
      <!-- this parameter MUST be set in minutes -->                                                                                                                                                         
      <name>tokenAliveTime</name>                                                                                                                                                                            
      <value>300</value>                                                                                                                                                                                     
    </value-param>                                                                                                                                                                                           
    <properties-param>                                                                                                                                                                                       
      <name>exo1</name>                                                                                                                                                                                      
      <property name="secret" value="81d1b5d080d1" />                                                                                                                                                        
      <property name="description" value="description" />                                                                                                                                                    
      <property name="provider.tokenRequestURL" value="http://localhost:8080/oauthprovider/request_token" />                                                                                                 
      <property name="provider.authorizationURL" value="http://localhost:8080/oauthprovider/authorize" />                                                                                                    
      <property name="provider.accessTokenURL" value="http://localhost:8080/oauthprovider/access_token" />                                                                                                   
    </properties-param>                                                                                                                                                                                      
  </init-params>                                                                                                                                                                                             
</component>                                                                                                                                                                                                 
<component>                                                                                                                                                                                                  
  <type>org.exoplatform.ws.security.oauth.impl.OAuthClientHttpImpl</type>                                                                                                                                    
</component>                                                                                                                                                                                                 
<component>                                                                                                                                                                                                  
  <type>org.exoplatform.ws.security.oauth.impl.OAuthTokenCleanerImpl</type>                                                                                                                                  
  <init-params>                                                                                                                                                                                              
  <value-param>                                                                                                                                                                                              
    <!-- this parameter MUST be set in minutes -->                                                                                                                                                           
    <name>tokenCleanerTimeout</name>                                                                                                                                                                         
    <value>3</value>                                                                                                                                                                                         
  </value-param>                                                                                                                                                                                             
  </init-params>                                                                                                                                                                                             
</component>

The client is redirected for authentication to the provider with the required parameters (request and secret token), before this token OAuthClient got from the provider (see configuration, property "provider.tokenRequestURL"). On the provider side, the user (if the authentication is successful and has valid request parameters) will be redirected to the consumer again (see property "callbackURL" in the provider configuration). Then the consumer (this is, the same as receiving request token, invisible for the client) receives the access token, and redirects the client to the original URL. Then the filter checks the token from the request and gives access to the requested resource.

you can find a sample here http://svn.exoplatform.org/svnroot/exoplatform/projects/portal/trunk/sample/application/cometd/

To have it working :

  • deploy a trunk version of the portal

  • go to ~/java/portal/trunk/sample/application/cometd/ and execute exoproject --deploy=module

  • start the portal

  • load all the widgets and portlets

  • in a page, add the cometd widget and the portlet "CometdDemo"

  • in the widget click on init

  • in the CometdDemo portlet, type a message and click on "send", a pop up with your message will appear on the top right and disappear after 3 sec

What happened?

  • the portlet (JSR 286) sent an action with the message.

  • the action called the continuation service and sent the message to the user that had sent it

  • the service sent the message to the browser

  • the cometd client side received the message, and sent it to topics

  • in topic, the notification window listened to the topic "/eXo/portal/notification" so the notification window appeared.

When we run Stress Testing Cometd (Jetty implementation) http://docs.codehaus.org/display/JETTY/Stress+Testing+Cometd we see that if we connect many clients, deliver message latency tends to grow up. This situation described in article "20,000 Reasons Why Comet Scales". http://cometdaily.com/2008/01/07/20000-reasons-that-comet-scales/ So, in order to support a lot of concurrent cometd connections we need a mean to horizontally scale eXo cometd support.

1- Getting base URL for cometd connection (one of the node in cometd cluster)

2 - Getting userToken at cometd server (it need for subscribing on channel)

3 - Sending message from Exo-server

LoadBalancer - component that give base URL of one of free cometd server.

ContinuationServiceDelegate - component that send message from Exo-server to client via cometd server there client is registered

RESTContinuationService - component that receive message from ContinuationServiceDelegate and delegate it to ContinuationServer and send userToken generated by ContinuationServer for user.

ContinuationService - component that provide cometd connection.

To start work with cometd service the client should send request to Exo-server with the username, receive with response URL one of cometd servers (1 - on the scheme). This URL gives out LoadBalancer using the information set in a configuration

<init-params>
  <object-param>
  <name>cometd.lb.configuration</name>
  <description>cometd lb nodes</description>
  <object type="org.exoplatform.ws.frameworks.cometd.loadbalancer.LoadBalancerImpl$LoadBalancerConf">
   <field name="nodes">
    <collection type="java.util.ArrayList">
     <value>
      <object type="org.exoplatform.ws.frameworks.cometd.loadbalancer.Node">
       <field name="id">
        <string>1</string>
       </field>
       <field name="url">
        <string>http://localhost:8080</string>
       </field>
       <field name="maxConnection">
        <int>10</int>
       </field>
      </object>
     </value>
     <value>
      <object type="org.exoplatform.ws.frameworks.cometd.loadbalancer.Node">
       <field name="id">
        <string>2</string>
       </field>
       <field name="url">
        <string>http://localhost:8081</string>
       </field>
       <field name="maxConnection">
        <int>15</int>
       </field>
      </object>
     </value>
    </collection>
   </field>
  </object>
 </object-param>
</init-params>

In the above configuration we described two cometd-cluseter nodes of. Let's consider one of them.

1 - are unique the identifier of node (id) http://localhost:8080 - base URL 15 - the maximum number of the connected clients. LoadBalancer chooses one of node in cometd cluster (on which connection is possible) and associate with a name of the user! Now the client know on that cometd server connection is possible, for this purpose it is necessary to request userToken on cometd server(2). As for this need do request on other domain client must use framework that allowed cross-domain-ajax. We have framework that can do this task, how use this framework describe in article - Framework for cross-domain AJAX. After client receive userToken the client can do cometd-registration.

Example: the Client with a name exo1 wishes to be connected to cometd to service. It is necessary to make HTTP request (GET) on Exo-server URL = "http://localhost:8080/rest/cometdurl/exo1" with the answer receives a base URL of cometd server, it can be as a remote server or local. We will assume that we use cluster and receive some thing like this "http://192.168.0.21:8080". Further it is necessary get userToken already from cometd server for this purpose do request (GET) URL ="http://192.168.0.22:8081/rest/gettoken/exo1", after that we do standard procedure for cometd connections.

Then it is necessary to send the message to client ContinuationServiceDelegate requests at LoadBalancer the cometd server address on which the client is registered sends the message in format JSON on cometd server should be RESTContinuationService which to accept the message and to transfer them to ContinuationService (3)

Example: we will want to send the message to the client exo1. For this purpose ContinuationServiceDelegate requests to LoadBalancer the information on that to what server the given user is connected, we will receive necessary URL http://192.168.0.22:8081) and do HTTP request (POST) on comets a server, URL = "http://192.168.0.22:8080/rest/sendprivatemessage/" in a body the message in format JSON is transferred, RESTContinuationService having received the given message transfers it ContinuationService which already in turn delivers it to the client.

<configuration>
 <component>
  <type>org.exoplatform.ws.frameworks.cometd.ContinuationService</type>
 </component>
 <component>
  <key>org.mortbay.cometd.continuation.AbstractBayeux</key>
  <type>org.mortbay.cometd.continuation.EXoContinuationBayeux</type>
 </component>
 <component>
  <key>org.exoplatform.ws.frameworks.cometd.transport.ContinuationServiceDelegate</key>
  <type>org.exoplatform.ws.frameworks.cometd.transport.ContinuationServiceRemoteDelegate</type>
 </component>
 <component>
  <type>org.exoplatform.ws.frameworks.cometd.transport.RESTContinuationService</type>
 </component>
 <component>
  <type>org.exoplatform.ws.frameworks.cometd.loadbalancer.RESTLoadBalancerService</type>
 </component>
 <component> 
  <key>org.exoplatform.ws.frameworks.cometd.loadbalancer.LoadBalancer</key>
  <type>org.exoplatform.ws.frameworks.cometd.loadbalancer.LoadBalancerImpl</type>
  <init-params>
  <object-param>
  <name>cometd.lb.configuration</name>
  <description>cometd lb nodes</description>
  <object type="org.exoplatform.ws.frameworks.cometd.loadbalancer.LoadBalancerImpl$LoadBalancerConf">
   <field name="nodes">
    <collection type="java.util.ArrayList">
     <value>
      <object type="org.exoplatform.ws.frameworks.cometd.loadbalancer.Node">
       <field name="id">
        <string>1</string>
       </field>
       <field name="url">
        <string>http://localhost:8080</string>
       </field>
       <field name="maxConnection">
        <int>10</int>
       </field>
      </object>
     </value>
     <value>
      <object type="org.exoplatform.ws.frameworks.cometd.loadbalancer.Node">
       <field name="id">
        <string>2</string>
       </field>
       <field name="url">
        <string>http://localhost:8081</string>
       </field>
       <field name="maxConnection">
        <int>15</int>
       </field>
      </object>
     </value>
    </collection>
   </field>
  </object>
 </object-param>
</init-params>
 </component>
</configuration>

The test: At testing running two servlet-containers Tomcat (in role Exo - server'a) and Jetty (cometd server). A configuration example:

<configuration clients="12" repeat="1" sleep-connection="500" sleep-sending="200">
  <container containerStart="false" port="8080" home=""/>
  <messages>
    <message broadcast="false" id="1">hello</message>
    <message broadcast="true" id="2">hello!!!</message>
  </messages>
  <cometd-url>http://localhost:8080/cometd/cometd</cometd-url>
  <base-url>http://localhost:8080/rest/</base-url>
  <channels>
    <channel>/eXo/comedt/test</channel>
  </channels>
</configuration>

configuration describe that we create 12 cometd connections with sleep-connection = "500" (мс), will be subscribe on the channel "/eXo/comedt/test". Then it will be sent two message "hello" "hello!!!" the First individually to each of the clients, the second broadcast on the channel. Messages delivered with an interval sleep-sending = "200". http://localhost:8080/rest/ - base URL of Exo - server. For start test execute a command:

mvn clean install -f pom-test.xml -Dexo.test.skip=false -Djetty.home = "./target/jetty"

(http://svn.exoplatform.org/projects/ws/branches/1.3/frameworks/cross-domain-ajax/)

There is a list of realized methods:

1. WebDAV Modifications to HTTP Operations:

2. WebDAV Property Operations:

3. WebDAV Lock Operations:

4. WebDAV Versioning Extension Operations:

5. WebDAV SEARCH Operation:

If there is no interested WebDAV method in the list above, you can use ExtensionMethod(handler, path, options) to construct WebDAV request you like with your own:

First parameter of each method - hanlder - is an object {onSuccess , onError, onComplete}, which describes three functions to call when the request either succeeds or fails, and completes:

Additionally you can add your own headers to the request by using the special parameter options.

Each method returns object result which consists next properties:

An example of using WebDAV library for the eXo Platform WebDAV-server:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Demonstration of the eXo Platform Client Library of WebDAV</title>
    <script type="text/javascript" src="js/webdav.js"></script>
    <script>    

// Example of using of eXo Platform Client Library of WebDAV
/**
 * Serialize an XML Document or Element and return it as a string.
 */
function XMLtoString(node) {
    if (typeof node != 'object') return node; 
    if (typeof XMLSerializer != "undefined")
        return (new XMLSerializer( )).serializeToString(node);
    else if (node.xml) return node.xml;
    else throw "XML.serialize is not supported or can't serialize " + node;
};

// get and setup webdav object
        var webdav = new Webdav('localhost', '8080');
            
        webdav.username = 'root';
        webdav.password = 'exo';

// define webdav methods handlers
        function handler_onSuccess(result) {
            alert('Request SUCCEEDED with status = ' + result.status + ': ' + result.statusstring);
        };

        function handler_onError(result) {
            alert('Request FAILED with status = ' + result.status + ': ' + result.statusstring);
        };

var handler = {
    onSuccess: handler_onSuccess,
    onError: handler_onError,
    onComplete: MKCOL_handler_onComplete
}

// for eXoPlatform webdav
var default_webdav_path = '/rest/jcr/repository/collaboration';

// create a collection 'test'
webdav.MKCOL(handler, default_webdav_path + '/test1');

// create a resource 'example.txt' with content 'an example'
        function MKCOL_handler_onComplete(result) {
    // create a resource 'example.txt' with content 'an example' 
    var options = {
content: 'an example',
content_type: 'text/plain; charset=UTF-8'
    }
    handler.onComplete = PUT_handler_onComplete;
    webdav.PUT(handler, default_webdav_path + '/test1/example.txt', options);
        };

// put resource example.txt under version control
        function PUT_handler_onComplete(result) {
    // put resource example.txt under version control  
    handler.onComplete = VERSIONCONROL_handler_onComplete;
        webdav.VERSIONCONTROL(handler, default_webdav_path + '/test1/example.txt');
        };

// obtain the 'version-tree' WebDAV report about 'example.txt'        
        function VERSIONCONROL_handler_onComplete(result) {
    // obtain the 'version-tree' WebDAV report about 'example.txt'
    var options = {
        depth: '0',
        type: 'version-tree'
    };
    handler.onComplete = REPORT_handler_onComplete;
    webdav.REPORT(handler, default_webdav_path + '/test1/example.txt', options);
        };

        // delete the collection 'test'
        function REPORT_handler_onComplete(result) {
            if ( result.content )
alert( 'Response of server: ' +  XMLtoString(result.content) );

            // delete the collection 'test'
    handler.onComplete = 'function() {}';
    webdav.DELETE(handler, default_webdav_path + '/test1/');
        };
    </script>
</head>
<body>
    <h2>Demonstration of the eXo Platform Client Library of WebDAV</h2>
</body>
</html>

Complete list of issues fixed in eXo-Ws Version 2.0. Find details on http://jira.exoplatform.org/secure/ReleaseNote.jspa?version=10432&styleName=Text&projectId=10070&Create=Create

Bug
* [WS-124] - Usage of SNAPSHOT in maven dependencies
* [WS-126] - jcifs 1.2.17 is not available in public maven repo, but 1.2.19 is.
* [WS-127] - wrong groupId for catalina dependency declaration


Improvement
* [WS-69] - oAuth should be able to use with annotation JSR250.
* [WS-74] - Add possibility transfer large data (files) via org.exoplatform.services.rest.impl.provider.MultipartFormDataEntityProvider
* [WS-77] - Check if the class org.exoplatform.ws.rest.ejbconnector30.RestEJBConnector can better handle the Portal Container to be compatible with multiple portal instance
* [WS-78] - Check if the class org.exoplatform.ws.rest.ejbconnector21.RestEJBConnectorBean can better handle the Portal Container to be compatible with multiple portal instance
* [WS-97] - Add support for X-HTTP-Method-Override header
* [WS-98] - Use URI pattern for request and response filters.


Task
* [WS-57] - Refactor eXo REST engine to compatibility with JSR-311.
* [WS-68] - Refactor REST EJB conector according to new implementation of REST engine (JSR-311). See WS-57.
* [WS-75] - Add WADL generation for request with OPTIONS HTTP method.
* [WS-81] - Change cometd service for JSR-311
* [WS-92] - Pre-release testing plan for QA team
* [WS-93] - Move webdav methods to the package org.exoplatform.services.rest.ext.method.webdav
* [WS-94] - More description of classes' purpose in rest.ext sources
* [WS-95] - Remove StandaloneContainerInitializedListener from JCR (one from WS has to be used everywhere), move PortalContainerInitializedFilter from JCR to WS
* [WS-117] - jsr-311 capability tests

Sub-task
* [WS-101] - Create service for administration groovy scripts for REST via HTTP.
* [WS-116] - Merge code to the branches/2.0

Complete list of issues fixed in eXo-Ws Version 2.0.1 . Find details on JIRA http://jira.exoplatform.org/secure/ReleaseNote.jspa?version=10631&styleName=Text&projectId=10070&Create=Create.

Bug
* [WS-125] - net.oauth wrong artifact id declared in maven dependencies
* [WS-137] - Error of processing the request with encoded in UTF-8 content by gadgets.io
* [WS-146] - In HierarchicalProperty class missing check QName.getPrefix() for length is 0
* [WS-159] - Need add charSet for Reader and Writer in JSON transformers, problem come in in Windows OS.
* [WS-165] - Incorrect maven dependency on net.oauth:net.oauth.core:20080229 
* [WS-166] - Generated files commited in svn
* [WS-177] - Connection problem with NTLM when trying to connect two users in same time
* [WS-183] - Use parent pom 1.1.1 in trunk

Improvement
* [WS-138] - Add supports for javax.ws.rs.core.Application in RESTful framework

New Feature
* [WS-176] - Create filter for URL rewriting
* [WS-182] - Create new simple Deploy Service method with JaxWsProxyFactoryBean

Task
* [WS-168] - Change URL format of groovy services to "/{command}/{repostory}/{workspace}/{path}"
* [WS-170] - oAuth in gadgets. Get protected resource from gadgets.
* [WS-171] - Migrate module configuration to use kernel_1_0.xsd
* [WS-172] - Remove unnecessary configuration from all the JARs' configuration.xml
* [WS-184] - Release WS-2.0.1