All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.util.Locked Maven / Gradle / Ivy

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

import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.lock.Lock;
import javax.jcr.lock.LockException;
import javax.jcr.lock.LockManager;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.jcr.observation.ObservationManager;

/**
 * Locked is a utility to synchronize modifications on a lockable
 * node. The modification is applied while the lock on the node is held, thus
 * ensuring that the modification will never fail with an {@link
 * javax.jcr.InvalidItemStateException}. This utility can be used with any
 * JCR Repository, not just Jackrabbit.
 * 

* The following example shows how this utility can be used to implement * a persistent counter: *

 * Node counter = ...;
 * long nextValue = ((Long) new Locked() {
 *     protected Object run(Node counter) throws RepositoryException {
 *         Property seqProp = counter.getProperty("value");
 *         long value = seqProp.getLong();
 *         seqProp.setValue(++value);
 *         seqProp.save();
 *         return new Long(value);
 *     }
 * }.with(counter, false)).longValue();
 * 
* If you specify a timeout you need to check the return value * whether the run method could be executed within the timeout * period: *
 * Node counter = ...;
 * Object ret = new Locked() {
 *     protected Object run(Node counter) throws RepositoryException {
 *         Property seqProp = counter.getProperty("value");
 *         long value = seqProp.getLong();
 *         seqProp.setValue(++value);
 *         seqProp.save();
 *         return new Long(value);
 *     }
 * }.with(counter, false);
 * if (ret == Locked.TIMED_OUT) {
 *     // do whatever you think is appropriate in this case
 * } else {
 *     // get the value
 *     long nextValue = ((Long) ret).longValue();
 * }
 * 
*/ public abstract class Locked { /** The mixin namespace */ private static final String MIX = "http://www.jcp.org/jcr/mix/1.0"; /** * Object returned when timeout is reached without being able to call * {@link #run} while holding the lock. */ public static final Object TIMED_OUT = new Object(); /** * Executes {@link #run} while the lock on lockable is held. * This method will block until {@link #run} is executed while holding the * lock on node lockable. * * @param lockable a lockable node. * @param isDeep true if lockable will be locked * deep. * @return the object returned by {@link #run}. * @throws IllegalArgumentException if lockable is not * mix:lockable. * @throws RepositoryException if {@link #run} throws an exception. * @throws InterruptedException if this thread is interrupted while waiting * for the lock on node lockable. */ public Object with(Node lockable, boolean isDeep) throws RepositoryException, InterruptedException { return with(lockable, isDeep, true); } /** * Executes {@link #run} while the lock on lockable is held. * This method will block until {@link #run} is executed while holding the * lock on node lockable. * * @param lockable a lockable node. * @param isDeep true if lockable will be locked * deep. * @param isSessionScoped true if the lock is session scoped. * @return the object returned by {@link #run}. * @throws IllegalArgumentException if lockable is not * mix:lockable. * @throws RepositoryException if {@link #run} throws an exception. * @throws InterruptedException if this thread is interrupted while waiting * for the lock on node lockable. */ public Object with(Node lockable, boolean isDeep, boolean isSessionScoped) throws RepositoryException, InterruptedException { return with(lockable, isDeep, Long.MAX_VALUE, isSessionScoped); } /** * Executes the method {@link #run} within the scope of a lock held on * lockable. * * @param lockable the node where the lock is obtained from. * @param isDeep true if lockable will be locked * deep. * @param timeout time in milliseconds to wait at most to acquire the lock. * @return the object returned by {@link #run} or {@link #TIMED_OUT} if the * lock on lockable could not be acquired within the * specified timeout. * @throws IllegalArgumentException if timeout is negative or * lockable is not * mix:lockable. * @throws RepositoryException if {@link #run} throws an exception. * @throws UnsupportedRepositoryOperationException * if this repository does not support * locking. * @throws InterruptedException if this thread is interrupted while * waiting for the lock on node * lockable. */ public Object with(Node lockable, boolean isDeep, long timeout) throws UnsupportedRepositoryOperationException, RepositoryException, InterruptedException { return with(lockable, isDeep, timeout, true); } /** * Executes the method {@link #run} within the scope of a lock held on * lockable. * * @param lockable the node where the lock is obtained from. * @param isDeep true if lockable will be locked * deep. * @param timeout time in milliseconds to wait at most to acquire the lock. * @param isSessionScoped true if the lock is session scoped. * @return the object returned by {@link #run} or {@link #TIMED_OUT} if the * lock on lockable could not be acquired within the * specified timeout. * @throws IllegalArgumentException if timeout is negative or * lockable is not * mix:lockable. * @throws RepositoryException if {@link #run} throws an exception. * @throws UnsupportedRepositoryOperationException * if this repository does not support * locking. * @throws InterruptedException if this thread is interrupted while * waiting for the lock on node * lockable. */ public Object with(Node lockable, boolean isDeep, long timeout, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, RepositoryException, InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout must be >= 0"); } Session session = lockable.getSession(); EventListener listener = null; try { // check whether the lockable can be locked at all String mix = session.getNamespacePrefix(MIX); if (!lockable.isNodeType(mix + ":lockable")) { throw new IllegalArgumentException("Node is not lockable"); } Lock lock = tryLock(lockable, isDeep, timeout, isSessionScoped); if (lock != null) { return runAndUnlock(lock); } if (timeout == 0) { return TIMED_OUT; } long timelimit; if (timeout == Long.MAX_VALUE) { timelimit = Long.MAX_VALUE; } else { timelimit = System.currentTimeMillis() + timeout; } // node is locked by other session -> register event listener if possible if (isObservationSupported(session)) { ObservationManager om = session.getWorkspace().getObservationManager(); listener = new EventListener() { public void onEvent(EventIterator events) { synchronized (Locked.this) { Locked.this.notify(); } } }; om.addEventListener(listener, Event.PROPERTY_REMOVED, lockable.getPath(), false, null, null, true); } // now keep trying to acquire the lock // using 'this' as a monitor allows the event listener to notify // the current thread when the lockable node is possibly unlocked for (; ;) { synchronized (this) { lock = tryLock(lockable, isDeep, timeout, isSessionScoped); if (lock != null) { return runAndUnlock(lock); } else { // check timeout if (System.currentTimeMillis() > timelimit) { return TIMED_OUT; } if (listener != null) { // event listener *should* wake us up, however // there is a chance that removal of the lockOwner // property is notified before the node is acutally // unlocked. therefore we use a safety net to wait // at most 1000 millis. this.wait(Math.min(1000, timeout)); } else { // repository does not support observation // wait at most 50 millis then retry this.wait(Math.min(50, timeout)); } } } } } finally { if (listener != null) { session.getWorkspace().getObservationManager().removeEventListener(listener); } } } /** * This method is executed while holding the lock. * @param node The Node on which the lock is placed. * @return an object which is then returned by {@link #with with()}. * @throws RepositoryException if an error occurs. */ protected abstract Object run(Node node) throws RepositoryException; /** * Executes {@link #run} and unlocks the lockable node in any case, even * when an exception is thrown. * * @param lock The Lock to unlock in any case before returning. * * @return the object returned by {@link #run}. * @throws RepositoryException if an error occurs. */ private Object runAndUnlock(Lock lock) throws RepositoryException { Node node = lock.getNode(); try { return run(node); } finally { node.getSession().getWorkspace().getLockManager().unlock(node.getPath()); } } /** * Tries to acquire a session scoped lock on lockable. * * @param lockable the lockable node * @param isDeep true if the lock should be deep * @param timeout time in milliseconds to wait at most to acquire the lock. * @param isSessionScoped true if the lock is session scoped. * @return The Lock or null if the * lockable cannot be locked. * @throws UnsupportedRepositoryOperationException * if this repository does not support locking. * @throws RepositoryException if an error occurs */ private static Lock tryLock(Node lockable, boolean isDeep, long timeout, boolean isSessionScoped) throws UnsupportedRepositoryOperationException, RepositoryException { try { LockManager lm = lockable.getSession().getWorkspace().getLockManager(); return lm.lock(lockable.getPath(), isDeep, isSessionScoped, timeout, null); } catch (LockException e) { // locked by some other session } return null; } /** * Returns true if the repository supports observation. * * @param s a session of the repository. * @return true if the repository supports observation. */ private static boolean isObservationSupported(Session s) { return "true".equalsIgnoreCase(s.getRepository().getDescriptor(Repository.OPTION_OBSERVATION_SUPPORTED)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy