JCR LOCKS IMPLEMENTATION Specification

Introduction

The main purpose of locking is the creation of a mechanism which allows a user to temporarily lock nodes in order to prevent other users from changing them.

The basic use cases are:

  • Placing a lock
    • Node.lock(boolean isDeep, boolean isSessionScoped) JCR 1.0 API
    • Node.lock(boolean isDeep, long timeOut) eXo JCR's ExtendedNode interface
  • removing a lock
    • Node.unlock()
    • Removing of the locked node by any user with sufficient access permissions as long as its parent node is not locked.
    • By configured time out (applicable for open-scoped locks).
    • After the session is closed if there are no more its holders (the sessions that hold a lockToken of this locking) and the locking of sessionScoped (the references to sessionScopedLockTokens are stored within the session).
    • If the Content Repository turns off, or turns on after a crash.
  • The check of locking during operations of writing.
    • Node.isLocked()
  • Reading information about locks and maintaining lock tokens(API).
    • Node.getLock().
    • Node.holdsLock().
    • Node.holdsLock().
    • Session.addLockToken(String token).
    • Session.getLockTokens().
    • Session.removeLockToken().
The user activity scheme:

lock.jpg

IMPLEMENTATION details

LockManager implements Startable, ItemsPersistenceListener, SessionLifecycleListener

  • Lock getLock(NodeImpl).
    • Tries to find the lockData of node or lockData of its nearest parent.
      • If such an object is not present it means that node is not locked and LockException will be thrown.
      • If the locking exists but the path differs we check if the locking is deep. If it's not than the current node is not locked and LockException will be thrown.
    • If all conditions are satisfied we return new LockImpl using LockData which we have found.
  • boolean isLocked(NodeData data,Session Id).
    • Tries to find in the list of locking corresponding LockData if such data is not present or we not have deep locking of parent it means what node is not locked.
  • boolean isLockHolder(NodeImpl).
    • Checks if the current sesion is the holder of LockToken for the current node.
  • boolean holdsLock(NodeData node).
    • We search for locking of the current nodde, if such exists, it means the node holds the lock.
  • void addPendingLock(NodeImpl, isDeep, boolean isSessionScoped, long timeOut).
    • Checks, whether the current node is locked or whether its parent is locked using a deep lock.
    • Checks, if it is necessary to add a deep lock, whether the child node is in the list of locking.
    • If all conditions are satisfied adds a new lock to the list of locking.
    • Adds a lock to the tokensMap using the key lockToken.
    • Adds a new lock to the pendingLocks list.
    • Writes the current state of locks into the file of locking.
  • onSaveItems().
    • Going over the logs, if we find a changesLog of type ExtendedEvent.LOCK, we add a new lock to the list of locks.
    • If we find a changesLog of type ExtendedEvent.UNLOCK, we remove a lock from the list of locks.
    • В логе неопределенного типа находим удаление заблокированных нод, если такие ноды в логе существуют, удаляем блокировку.In the changesLog of an undefined type we find the fact of deletion of locked nodes. If such nodes exist in the changesLog, we remove the lock.
  • onCloseSession(SessionImpl session).
    • Deletes all SessionScoped locks for which the current session is the holder of locking.
  • String[] getLockTokens(String sessionID).
    • Goes over the list of locks and collects all lockTokens that are accessible to the session .
  • addLockToken(String sessionId, String lt).
    • Using the tokensMap list we find LockData which conform with lockToken.
    • Add a session's sessionId as a LockHolder.
  • removeLockToken (String sessionId, String lt).
    • Using the tokensMap list we find LockData which conform with lockToken.
    • Delete the sesiin session from the list of lock holders.
  • removeLock(String sessionId, String nodeUuid).
    • Checks whether the given node is really locked and also whether the parent of this node is not locked.
    • Deletes locking from the list of locking.Sets setLive of LockData to false.
LockImpl object implements Lock and contains fields

  • LockData data.
  • SessionImpl session.
LockData object

  • contains fields
    • boolean deep.
    • boolean sessionScoped.
    • String lockToken.
    • String owner - The user who places a lock on a node.
    • String nodeUuid - The uuid of the node on which a lock was created.
    • birthday - The time when the lock was created.
    • timeOut.
    • Set lockHolders.
  • Methods
    • getLockToken(String sessionId) - Returns lockToken if sessionId is the holder of locking, otherwise return null.
    • addLockHolder(String sessionId) - Adds sessionId to the list of lockHolder.
    • removeLockHolder(String sessionId) Ц Removes sessionId from the list of lock holders.
    • isLockHolder(String sessionId) - Determines whether a session is the holder of locking.
    • getLockHolderSize() - Returns the amount of sessions, holders of locking.
    • getTimeToDeath() - Returns time in seconds before locking is automatically removed.
    • Refresh() - Increases the life-time of locking on timeOut seconds.

Configuration

Since 1.6 JCR version you can configure some parameters of locking process.

Version 1.6

In this version you can only configure the timing out of locks. See also: section 8.4.9 “Timing Out” of JCR-170 specification.

.......
<workspaces>
 <workspace name="ws" auto-init-root-nodetype="nt:unstructured">
.......
   <lock-time-out>15m</lock-time-out><!-- the open-scoped lock will be unlocked in 15 min -->
 </workspace>
.......

Vertsion 1.7 and higher

Implementation of lock in version 1.7 of JCR has some changes and improvements.

  • LockManager configuration is presented as a separate tag.
  • Add functionality of Lock persisters.
.......
<workspaces>
 <workspace name="ws" auto-init-root-nodetype="nt:unstructured">
  .......
   <lock-manager>
    <time-out>15m</time-out><!-- the open-scoped lock will be unlocked in 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>
.......

where path - a lock folder, each workspace has its own.

Persister

Lock persisters is a component which implements the LockPersister interface.

public interface LockPersister extends Startable {
  /**
   * Add lock information to the persistent storage
   * 
   * @param lock
   * @throws RepositoryException
   */
  void add(LockData lock) throws LockException;

  /**
   * Remove a lock from the persistent storage
   * 
   * @param lock
   * @throws RepositoryException
   */
  void remove(LockData lock) throws LockException;

  /**
   * Remove all locks from the persistent storage
   * 
   * @throws RepositoryException
   */
  void removeAll() throws LockException;
}

Lock persisters is a component which implements the LockPersister interface. The main purpose of LockPersisters is to store additional information about the lock.

One of the implementation of LockPersisters is FileSystemLockPersister which stores information about locks in the file system and removes all locked information from JCR when the repository starts or restarts. FileSystemLockPersister has one parameter: the path to the folder where it can store the information.


Creator: Gennady Azarenkov on 2007/05/26 10:29
Copyright (c) 2000-2009. Allright reserved - eXo platform SAS
1.6.13286