This article covers a Hippo CMS version 11. There's an updated version available that covers our most recent release.

JCR Session Pooling Repository

1. Introduction

Most applications support interaction with a JCR repository. Creating a new JCR session for each user can be time consuming, in order to perform a JCR transaction that might take a milliseconds. Very often, opening a JCR session per user can be unfeasible in a publicly-hosted Internet application where the number of simultaneous users can be very large. In this case, developers often wish to share a "pool" of open JCR sessions between all of the application's current users. The number of users actually performing a request at any given times is usually a very small percentage of the total number of active users, and during request processing is the only time that a JCR session is required. The application itself logs into the JCR repository, and handles any user account issues internally.

HST Session Pools package relies on code in the commons-pool package to provide the underlying object pool mechanisms that it utilizes.

Also, HST Session Pool Components provides MultipleRepository and LazyMultipleRepository components as well as BasicPoolingRepository to support more sophisticated session pools usages.

2. Basic Pooling Repository component implementing javax.jcr.Repository

HST Session Pooling package adopted the strategy of Commons DBCP by implementing javax.jcr.Repository. HST Session Pooling package decorates JCR sessions to allow developers to depend on the standard API without concerning the internal handling of the session pool.

There are the following commonalities between javax.sql.DateSource and javax.jcr.Repository:

  • Both are designed as an entry point to get a Connection or Session

  • DataSource#getConnection() methods seem similar to Repository#login() methods.

Therefore, by having HST Session Pooling Component implement javax.jcr.Repository interface, developers can simply depend on the JCR APIs only, internally using JCR Session Pooling Components which is configured as a JNDI resource like the following example.

Context initCtx = new InitialContext();
Context envCtx = initCtx.lookup("java:comp/env");
// retrieves the pooling repository by JNDI look up.
Repository repository = (Repository) envCtx.lookup("jcr/repository");
Session jcrSession =
       repository.login(new SimpleCredentials("admin", "admin".toCharArray());
// use session
// ...
// returns the session to the pooling repository via the #logout() method.
jcrSession.logout();

//onehippo-prod.global.ssl.fastly.net/binaries/ninecolumn/content/gallery/connect/library/hst-pool.png

In the diagram above, the core JCR Session Pooling Repository component is BasicPoolingRepository, which wraps the internal repository and manages JCR session pools. Clients can borrow a pooled JCR session by #login() methods and return the session by #logout() method on the session object.

3. Basic Pooling Repository Configuration

Basic Pooling Repository components can be configured in a Spring Framework XML Configuration file.

For example, the default Session Pooling Repository component is configured in hst-core-2.xx.xx.jar!/org/hippoecm/hst/site/container/SpringComponentManager-jcr.xml as follows:

<bean class="org.hippoecm.hst.core.jcr.pool.BasicPoolingRepository"
  init-method="initialize" destroy-method="close">
  <!-- delegated JCR repository -->
  <property name="repositoryProviderClassName"
            value="${repositoryProviderClassName}" />
  <property name="repositoryAddress"
            value="${default.repository.address}"/>
  <property name="defaultCredentialsUserID"
            value="${default.repository.user.name}
                   ${repository.pool.user.name.separator}
                   ${default.repository.pool.name}"/>
  <property name="defaultCredentialsUserIDSeparator"
            value="${repository.pool.user.name.separator}"/>
  <property name="defaultCredentialsPassword"
            value="${default.repository.password}"/>
  <property name="hstJmvEnabledUsers" ref="hstJmvEnabledUsers"/>
  <!-- Pool properties. Refer to the GenericObjectPool of
       commons-pool library. -->
  <property name="maxActive" value="${default.repository.maxActive}"/>
  <property name="maxIdle" value="${default.repository.maxIdle}"/>
  <property name="minIdle" value="0"/>
  <property name="initialSize" value="0"/>
  <property name="maxWait" value="10000"/>
  <property name="testOnBorrow" value="true"/>
  <property name="testOnReturn" value="false"/>
  <property name="testWhileIdle" value="false"/>
  <property name="timeBetweenEvictionRunsMillis" value="60000"/>
  <property name="numTestsPerEvictionRun" value="1"/>
  <property name="minEvictableIdleTimeMillis" value="300000"/>
  <property name="refreshOnPassivate" value="true"/>
  <property name="maxRefreshIntervalOnPassivate"
            value="${sessionPool.maxRefreshIntervalOnPassivate}"/>
  <property name="poolingCounter" ref="defaultPoolingCounter" />
  <property name="maxTimeToLiveMillis" 
            value="${default.repository.maxTimeToLiveMillis}"/>
</bean>

The variable expressions such as ${default.repository.address} should be defined in /WEB-INF/hst-config.properties or should be provided in SpringComponentManager.properties by the system by default.

Here are full details of configurable parameters.

Parameter

Default

Description

repositoryProviderClassName

JcrHippoRepositoryProvider (under org.hippoecm.hst.core.jcr.pool package)

The class name of the JCR repository provider. The default provider, JcrHippoRepositoryProvider, creates a HippoRepository by using HippoRepositoryFactory.

repositoryAddress

 

The JCR Repository URL to create a repository with the JCR repository provider

defaultCredentialsUserID

 

The default username to establish a JCR session

defaultCredentialsUserIDSeparator

@

The separator between the repository username and pool-specific suffix.
For example, defaultCredentialsUserID can be configured like " siteuser@default", meaning the repository username is " admin" with a suffix, " @default".
So, the Pool uses " admin" internally to retrieve a JCR session, but the pool is differentiated by the suffix even though there are many pools with the same credentials. Please refer to the section explaining MultipleRepository for details.

defaultCredentialsPassword

 

The default password to establish a JCR session

maxActive

100

The maximum number of active JCR sessions that can be allocated from this pool at the same time, or netagive for no limit.

maxIdle

25

The maximum number of JCR sessions that can remain idle in the pool, without extra ones being released, or negative for no limit

minIdle

0

The maximum number of JCR sessions that can remain idle in the pool, without extra ones being created, or zero to create none

initialSize

0

The initial number of JCR sessions that are created when the pool is started.

maxWait

indefinitely
(-1)

The maximum number of milliseconds that the pool will wait (when there are no available JCR sessions) for a JCR session to be returned before throwing an exception, or -1 to wait indefinitely

validationQuery

 

The JCR query that will be used to validate JCR sessions from this pool before returning them to the caller.
NOTE: Even though you do not configure this validation query, the pool validates session objects through Session#isLive() method by default.

testOnBorrow

true

The indication of whether objects will be validated before being borrowed from the pool. If the object fails to validate, it will be dropped from the pool, and we will attempt to borrow another.

testOnReturn

false

The indication of whether objects will be validated before being returned to the pool.

testWhileIdle

false

The indication of whether objects will be validated by the idle object evictor (if any). If an object fails to validate, it will be dropped from the pool.

timeBetweenEvictionRunsMillis

- 1

The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no idle object evictor thread will be run.

numTestsPerEvictionRun

3

The number of objects to examine during each run of the idle object evictor thread (if any).

minEvictableIdleTimeMillis

1000 * 60 * 3

The minimum amount of time an object may sit idle in the pool before it is eligible for eviction by the idle object evictor (if any).

refreshOnPassivate

true

The indication of whether session objects will be refreshed when they are passivated (returned to the pool).

maxRefreshIntervalOnPassivate

300000

The number of milliseconds that this pool can refresh the passivated session objects which were refreshed more than this configured interval time before.

sessionsRefreshPendingTimeMillis

0

The timestamp that this pool should refresh JCR sessions if they were refreshed before this timestamp. Only when this value is set to a positive number, this will be effective.

keepChangesOnRefresh

false

The indication of whether session objects will be refreshed with keepChanges option.

whenExhaustedAction

block

The behavior of borrowing a JCR session when the pool is exhausted.

  • block - borrowing will be blocked until a new or idle session is available. If a positive maxWait value is supplied, borrowing will be blocked for at most that many miliseconds, after which a NoAvailableSessionException will be thrown.
    IF maxWait is non-positive, borrowing will be blocked indefintely.

  • fail - borrowing will throw a NoAvailableSessionException if there's no idle session is available and no new JCR session can be created.

  • grow - borrowing will create a new JCR session, which essentially makes maxActive meaningless.

poolingCounter

an instance of org.hippoecm.hst.core.jcr.pool.DefaultPoolingCounter
 

Object used to be able to monitor session pool usage via JMX Beans. By default, JMX bean support for pools is enabled

maxTimeToLiveMillis maximum time a JCR session from a pool is allowed to live Defaults to 1 hour

4. MultipleRepository component

BasicPoolingRepository plays the role of managing JCR sessions in a pool. However, many applications need to use multiple JCR session pools for different purposes. For example, a JCR session pool only for binary resources access and another JCR session pool for editing document contents, etc.

Again, it would be simpler if we can simply use JCR API only to access this multiple repository component with underlying session pools.

MultipleRepository component plays this role of delegating the Repository API calls to underlying BasicPoolingRepository components by finding a proper Session pool from the credentials provided by the client.

org.hippoecm.hst.core.jcr.pool.MultipleRepository interface represents the functionality of this component, which extends javax.jcr.Repository.

So, with its login() methods, clients can access its underlying JCR session pools transparently by JCR credentials.

For example, suppose a session pool is configured with username, "siteuser", and another session pool is configured with username, "writer". If a client invokes #login() method on MultipleRepository component with credentials of "siteuser", then the first session pool is selected and the call is delegated to the session pool. If a client invokes #login() method with credentails of "writer", then the second session pool is selected.

But what if the credentials are the same between two different session pools - in that case, the MultipleRepository component cannot differentiate two different session pools. So, we provide a special delimiter in the repository username. The delimiter is set to "@", by default.

For example, you can use "siteuser@default" for the first session pool, and "siteuser@binaries". So, if a client invokes #login() method on MultipleRepository component with credentials of "siteuser@default" username, then the first session pool is selected. If a client invokes #login() method with credentials of "siteuser@binaries" username, then the second session pool is selected.

MultipleRepository can be configured as follows:

<bean id="javax.jcr.Repository"
      class="org.hippoecm.hst.core.jcr.pool.MultipleRepositoryImpl">
  <!-- Delegating session pool repositories map -->
  <constructor-arg>
    <map>
      <entry key-ref="javax.jcr.Credentials.default">
        <!-- A BasicPoolingRepository here -->
        <bean class="org.hippoecm.hst.core.jcr.pool.BasicPoolingRepository"
              init-method="initialize" destroy-method="close">
          <!-- SNIP -->
        </bean>
      </entry>
      <!-- SNIP -->
      <entry key-ref="javax.jcr.Credentials.binaries">
        <!-- A BasicPoolingRepository here -->
        <bean class="org.hippoecm.hst.core.jcr.pool.BasicPoolingRepository"
              init-method="initialize" destroy-method="close">
          <!-- SNIP -->
        </bean>
      </entry>
    </map>
  </constructor-arg>
  <!-- The default credentials for login() without credentials argument -->
  <constructor-arg ref="javax.jcr.Credentials.default" />
</bean>

As you see above, the MultipleRepository component can be created with two constructor arguments. The first constructor argument is the map of underlying JCR session pools with credentials keys. The second constructor argument is the default JCR credentials which is used when #login() method without any parameter is invoked.

5. LazyMultipleRepository component

MultipleRepository is good when there are predefined set of session pools. However, sometimes, it would be very useful if some session pools are created on-demand, that is, created on the first request with credentials.

For example, a MultipleRepository is delegating to some internal session pools. However, it should be possible to create an internal session pool if the internal session pool is not found yet in the predefined map.

To fulfill this requirement, LazyMultipleRepository component is provided.

LazyMultipleRepository is configured as follows in SpringComponentManager-jcr.xml by default:

<bean id="javax.jcr.Repository"
      class="org.hippoecm.hst.core.jcr.pool.LazyMultipleRepositoryImpl">
  <!-- Delegating session pool repositories map -->
  <constructor-arg>
    <map>
      <entry key-ref="javax.jcr.Credentials.default">
        <!-- A BasicPoolingRepository here -->
        <bean class="org.hippoecm.hst.core.jcr.pool.BasicPoolingRepository"
              init-method="initialize" destroy-method="close">
          <!-- SNIP -->
        </bean>
      </entry>
      <!-- SNIP -->
      <entry key-ref="javax.jcr.Credentials.binaries">
        <!-- A BasicPoolingRepository here -->
        <bean class="org.hippoecm.hst.core.jcr.pool.BasicPoolingRepository"
              init-method="initialize" destroy-method="close">
          <!-- SNIP -->
        </bean>
      </entry>
    </map>
  </constructor-arg>
  <!-- The default credentials for login() without credentials argument -->
  <constructor-arg ref="javax.jcr.Credentials.default" />
  <!-- Default configuration map to be used in creating BasicPoolingRepository
       components on-demand. -->
  <constructor-arg>
    <map key-type="java.lang.String" value-type="java.lang.String">
      <entry key="repositoryProviderClassName"
             value="${repositoryProviderClassName}" />
      <entry key="repositoryAddress" value="${default.repository.address}"/>
      <!-- SNIP -->
      <entry key="maxActive" value="${disposable.repository.maxActive}"/>
      <!-- SNIP -->
    </map>
  </constructor-arg>
  <property name="timeBetweenEvictionRunsMillis"
        value="${disposable.global.repository.timeBetweenEvictionRunsMillis}"/>
  <property name="disposableUserIDPattern" value=".*;disposable"/>
</bean>

There are two additional properties for LazyMultipleRepository component.

Parameter

Default

Description

timeBetweenEvictionRunsMillis

0

Session pools can be evicted if they are used any more. (If a session pool has 0 idle sessions and zero active sessions, then it is regarded as non-used pool.)
To achieve this, an evictor can be created an run internally. This property configures the running interval time milliseconds for the evictor.
Only when this value is set to a positive number, an evictor is created an run.

disposableUserIDPattern

 

Regular expression string to try matching against the repository credentials username during eviction process.
If this is configured, session pools matched with this pattern can be evicted.
Otherwise, no session pool can be evicted.

6. JNDI Resources

Two javax.naming.spi.ObjectFactory implementations are provided: BasicPoolingRepositoryFactory and MultiplePoolingRepositoryFactory.

So, for example with Tomcat, JNDI Resource for a JCR session pool can be configured in the application context descriptor like the following example:

<Context>
  <Resource name="jcr/repository" auth="Container"
    type="javax.jcr.Repository"
    factory="org.hippoecm.hst.core.jcr.pool.BasicPoolingRepositoryFactory"
    repositoryAddress="rmi://127.0.0.1:1099/hipporepository"
    defaultCredentialsUserID="admin"
    defaultCredentialsPassword="admin"
    maxActive="250"
    maxIdle="50"
    initialSize="0"
    maxWait="10000"
    testOnBorrow="true"
    testOnReturn="false"
    testWhileIdle="false"
    timeBetweenEvictionRunsMillis="60000"
    minEvictableIdleTimeMillis="60000" />
  </Resource>
</Context>

With the configuration above, you can configure a session pool of BasicPoolingRepository.

MultiplePoolingRepositoryFactory allows you to create a MultipleRepository component which can delegate calls to multiple session pools. In the following example, two session pools are configured and each property configuration is separated by a comma.

<Context>
  <Resource name="jcr/repository" auth="Container"
    type="javax.jcr.Repository"
    factory="org.hippoecm.hst.core.jcr.pool.MultiplePoolingRepositoryFactory"
    repositoryAddress="rmi://127.0.0.1:1099/hipporepository,
                       rmi://127.0.0.1:1099/hipporepository"
    defaultCredentialsUserID="admin, editor"
    defaultCredentialsPassword="admin, editor"
    maxActive="250, 250"
    maxIdle="50, 50"
    initialSize="0, 0"
    maxWait="10000, 10000"
    testOnBorrow="true, true"
    testOnReturn="false, false"
    testWhileIdle="false, false"
    timeBetweenEvictionRunsMillis="60000, 60000"
    minEvictableIdleTimeMillis="60000, 60000" />
  </Resource>
</Context>

To use the JNDI resources configured above, you should declare those in your web.xml like the following:

  <resource-ref>
    <description>JCR Repository</description>
    <res-ref-name>jcr/repository</res-ref-name>
    <ref-type>javax.jcr.Repository</res-type>
    <res-auth>Container</res-auth>
  </resource-ref>

Finally, you can write codes in JSP pages or Java classes to use the resources like the following example:

<%@ page language="java" import="javax.jcr.*, javax.naming.*" %>
<%
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
// look up the session pool
Repository repository = (Repository) envCtx.lookup("jcr/repository");
// borrow a JCR session with login() method.
Session jcrSession = repository.login(new SimpleCredentials("admin", "admin".toCharArray());
// do something ...
// ...
// return the JCR session to the pool with logout() method.
jcrSession.logout();
%>

.