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();
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. |
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 negative 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 |
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. |
|
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.
|
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 | Defaults to 1 hour | Maximum time a JCR session from a pool is allowed to live |
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.) |
disposableUserIDPattern |
Regular expression string to try matching against the repository credentials username during eviction process. |
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(); %>
.