EmailLinkedInGoogle+TwitterFacebook

Session is the facade with which applications communicate with the underlying JCR repository. It is the application writer’s job to manage these session appropriately. Sessions are not thread safe and therefore it is advisable to use and discard a session as early as possible. I have created a utility class ‘RepositoryUtil’ to help manage my sessions that I will be using in rest of the application. At this stage I am using an embedded repository and I am not doing any custom configuration of the repository resources. When we reach the stage where a simple embedded repository is not sufficient for our needs I will refactor this class.

Source code available @
svn checkout https://whiteboardjunkie.googlecode.com/svn/trunk/jackrabbittutorial jackrabbittutorial

package org.boni.jrtutorial.util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jcr.Repository;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;

import org.apache.jackrabbit.core.TransientRepository;

/**
 * An utility class that manages the sessions.  
 * Simple implementation uses a transient repository
 * and allows any number of logins with the same id.
 * Session is wrapped a SessionWrapper class.
 * The session wrapper uniquely identifies each session with
 * an unique id. While it is advisable to call the logout(sessionid)
 * method to release the resources, finalization constructs will take care
 * of releasing resources anyways.
 * @author Boni.G
 *
 */
public class RepositoryUtil {
	static Map<string, list=""><sessionwrapper>> currentSessions;
	static Repository aRepository;
	/**
	 * Use this static method to login to the repository.  The repository 
	 * currently uses the SimpleLoginModule that will authorize any tom, dick
	 * and even harry.  In a production environments it is advisable to wrap
	 * the suitable authentication mechanism as a JAAS module.  We will be
	 * discussing all these and more during the next few posts. 
	 * @param userName
	 * @param password
	 * @return
	 */
	public static SessionWrapper login(String userName, String password){
		try {
			Repository repository = getRepositoryHandle();
			Session aSession = repository.login(new SimpleCredentials(userName,password.toCharArray()));
			if (currentSessions == null)
				initializeSessionsMap();
			List<sessionwrapper> sessions = currentSessions.get(userName);
			if (sessions == null)
				sessions = Collections.synchronizedList(new ArrayList<sessionwrapper>());
			SessionWrapper wrappedSession = new SessionWrapper( aSession); 
			sessions.add(wrappedSession);
			currentSessions.put(userName, sessions);
			return wrappedSession;
		} catch (Exception e) {
			throw new RuntimeException(e);
		} 
	}
	
	/**
	 * Logout will release the resources being used by the session.
	 * @param sessionId
	 */
	public static void logout(String sessionId){
		if (currentSessions == null) return;
		Set<string> userNames = currentSessions.keySet();
		for (String aName : userNames){
			List<sessionwrapper> sessions = currentSessions.get(aName);
			if (logout(sessions, sessionId)){
				return;
			}
		}
		
	}
	/**
	 * The method that actually does the session logout.
	 * @param sessions
	 * @param sessionId
	 * @return
	 */
	private synchronized static boolean logout(List<sessionwrapper> sessions, String sessionId){
		if (sessions == null || sessionId == null || "".equals(sessionId)) return false;
		for (SessionWrapper aWrapper : sessions){
			if (sessionId.equals(aWrapper.getId())){
				sessions.remove(aWrapper);
				aWrapper.logout();
				aWrapper = null;
				return true;
			}
		}
		return false;
	}
	private static void initializeSessionsMap(){
		currentSessions = Collections.synchronizedMap(new HashMap<string, list=""><sessionwrapper>>());
	}
	private static Repository getRepositoryHandle(){
		try {
			if (aRepository == null)
				aRepository = new TransientRepository("classpath:repository.xml","target/repository");
			return aRepository;
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
}

‘login’ will create a new SessionWrapper object that wraps the session object and store it in a HashMap of List. The HashMap currentSessions has the key as the ‘userName’. So multiple logins with the same username will yield a new Session. Application has the option of using the session as much as it needs and then release the resources by calling RepositoryUtil.logout(String sessionId).

An obvious question at this stage is ‘Who is a valid user?’. Jackrabbit defines validity of a login through a configurable JAAS module. The repository configurations are specified through , repository.xml file. In our examples so far we have not customized the repository.xml. So we are now using the default repository.xml file. The default looks like this:


<!--<br/>   Licensed to the Apache Software Foundation (ASF) under one or more<br/>   contributor license agreements.  See the NOTICE file distributed with<br/>   this work for additional information regarding copyright ownership.<br/>   The ASF licenses this file to You under the Apache License, Version 2.0<br/>   (the "License"); you may not use this file except in compliance with<br/>   the License.  You may obtain a copy of the License at<br/><br/>       http://www.apache.org/licenses/LICENSE-2.0<br/><br/>   Unless required by applicable law or agreed to in writing, software<br/>   distributed under the License is distributed on an "AS IS" BASIS,<br/>   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.<br/>   See the License for the specific language governing permissions and<br/>   limitations under the License.<br/>-->

                            "http://jackrabbit.apache.org/dtd/repository-1.5.dtd">
<!-- Example Repository Configuration File<br/>     Used by<br/>     - org.apache.jackrabbit.core.config.RepositoryConfigTest.java<br/>     -<br/>-->
<repository>
    <!--<br/>        virtual file system where the repository stores global state<br/>        (e.g. registered namespaces, custom node types, etc.)<br/>    -->
    <filesystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
        <param name="path" value="${rep.home}/repository"> </param>
    </filesystem>

    <!--<br/>        security configuration<br/>    -->
    <security appname="Jackrabbit">
        <!--<br/>            security manager:<br/>            class: FQN of class implementing the JackrabbitSecurityManager interface<br/>        -->
        <securitymanager class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager" workspacename="security">
            <!--<br/>            workspace access:<br/>            class: FQN of class implementing the WorkspaceAccessManager interface<br/>            -->
            <!-- <WorkspaceAccessManager class="..."/> -->
            <!-- <param name="config" value="${rep.home}/security.xml"> </param> -->
        </securitymanager>

        <!--<br/>            access manager:<br/>            class: FQN of class implementing the AccessManager interface<br/>        -->
        <accessmanager class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager">
            <!-- <param name="config" value="${rep.home}/access.xml"> </param> -->
        </accessmanager>

        <loginmodule class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
           <!-- <br/>              anonymous user name ('anonymous' is the default value)<br/>            -->
           <param name="anonymousId" value="anonymous"> </param>
           <!--<br/>              administrator user id (default value if param is missing is 'admin')<br/>            -->
           <param name="adminId" value="admin"> </param>
        </loginmodule>
    </security>

    <!--<br/>        location of workspaces root directory and name of default workspace<br/>    -->
    <workspaces rootpath="${rep.home}/workspaces" defaultworkspace="default">
    <!--<br/>        workspace configuration template:<br/>        used to create the initial workspace if there's no workspace yet<br/>    -->
    <workspace name="${wsp.name}">
        <!--<br/>            virtual file system of the workspace:<br/>            class: FQN of class implementing the FileSystem interface<br/>        -->
        <filesystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
            <param name="path" value="${wsp.home}"> </param>
        </filesystem>
        <!--<br/>            persistence manager of the workspace:<br/>            class: FQN of class implementing the PersistenceManager interface<br/>        -->
        <persistencemanager class="org.apache.jackrabbit.core.persistence.bundle.DerbyPersistenceManager">
          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"> </param>
          <param name="schemaObjectPrefix" value="${wsp.name}_"> </param>
        </persistencemanager>
        <!--<br/>            Search index and the file system it uses.<br/>            class: FQN of class implementing the QueryHandler interface<br/>        -->
        <searchindex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
            <param name="path" value="${wsp.home}/index"> </param>
            <param name="textFilterClasses" value="org.apache.jackrabbit.extractor.PlainTextExtractor,org.apache.jackrabbit.extractor.MsWordTextExtractor,org.apache.jackrabbit.extractor.MsExcelTextExtractor,org.apache.jackrabbit.extractor.MsPowerPointTextExtractor,org.apache.jackrabbit.extractor.PdfTextExtractor,org.apache.jackrabbit.extractor.OpenOfficeTextExtractor,org.apache.jackrabbit.extractor.RTFTextExtractor,org.apache.jackrabbit.extractor.HTMLTextExtractor,org.apache.jackrabbit.extractor.XMLTextExtractor"> </param>
            <param name="extractorPoolSize" value="2"> </param>
            <param name="supportHighlighting" value="true"> </param>
        </searchindex>
    </workspace>

    <!--<br/>        Configures the versioning<br/>    -->
    <versioning rootpath="${rep.home}/version">
        <!--<br/>            Configures the filesystem to use for versioning for the respective<br/>            persistence manager<br/>        -->
        <filesystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
            <param name="path" value="${rep.home}/version"> </param>
        </filesystem>

        <!--<br/>            Configures the persistence manager to be used for persisting version state.<br/>            Please note that the current versioning implementation is based on<br/>            a 'normal' persistence manager, but this could change in future<br/>            implementations.<br/>        -->
        <persistencemanager class="org.apache.jackrabbit.core.persistence.bundle.DerbyPersistenceManager">
          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"> </param>
          <param name="schemaObjectPrefix" value="version_"> </param>
        </persistencemanager>
    </versioning>

    <!--<br/>        Search index for content that is shared repository wide<br/>        (/jcr:system tree, contains mainly versions)<br/>    -->
    <searchindex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
        <param name="path" value="${rep.home}/repository/index"> </param>
        <param name="textFilterClasses" value="org.apache.jackrabbit.extractor.PlainTextExtractor,org.apache.jackrabbit.extractor.MsWordTextExtractor,org.apache.jackrabbit.extractor.MsExcelTextExtractor,org.apache.jackrabbit.extractor.MsPowerPointTextExtractor,org.apache.jackrabbit.extractor.PdfTextExtractor,org.apache.jackrabbit.extractor.OpenOfficeTextExtractor,org.apache.jackrabbit.extractor.RTFTextExtractor,org.apache.jackrabbit.extractor.HTMLTextExtractor,org.apache.jackrabbit.extractor.XMLTextExtractor"> </param>
        <param name="extractorPoolSize" value="2"> </param>
        <param name="supportHighlighting" value="true"> </param>
    </searchindex>
</workspaces>

This file is available to you through the jackrabbit-core jar. To cut the long story short the login right now is validated through :

        <loginmodule class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
           <!-- <br/>              anonymous user name ('anonymous' is the default value)<br/>            -->
           <param name="anonymousId" value="anonymous"> </param>
           <!--<br/>              administrator user id (default value if param is missing is 'admin')<br/>            -->
           <param name="adminId" value="admin"> </param>
        </loginmodule>

This class deems every login valid. So currently we are letting every tom dick and harry in. However we will be implementing an application specific login module shortly. That brings me to the interesting discussion of what the example application is going to be. The good news is that I have a fairly good idea on that and I will discuss it in the next post.

One Thought on “Managing JCR Sessions

  1. There have been a few minor changes to the RepositoryUtil.java since I wrote the post. In short I added a Runtime Shutdown hook to cleanup the resources and it seems to be working fine for the test cases. Keep in mind that the code will evolve progressively and I will try to make it as close as possible to enterprise class as I would like it to be. There are different resource management requirements for different types os usage scenarios.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Post Navigation