You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2011/03/04 15:29:09 UTC

svn commit: r1077966 - in /jackrabbit/trunk: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/

Author: jukka
Date: Fri Mar  4 14:29:09 2011
New Revision: 1077966

URL: http://svn.apache.org/viewvc?rev=1077966&view=rev
Log:
JCR-2874: Locked helper class improvements

Patch by Alex Parvulescu

Added:
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/LockedWrapperTest.java
    jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/LockedWrapper.java
Modified:
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/LockTest.java

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/LockTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/LockTest.java?rev=1077966&r1=1077965&r2=1077966&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/LockTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/LockTest.java Fri Mar  4 14:29:09 2011
@@ -29,7 +29,8 @@ import java.util.Iterator;
 import java.util.Random;
 
 /**
- * <code>LockTest</code> tests the utility {@link org.apache.jackrabbit.util.Locked}.
+ * <code>LockTest</code> tests the utility
+ * {@link org.apache.jackrabbit.util.Locked}.
  */
 public class LockTest extends AbstractJCRTest {
 
@@ -41,40 +42,43 @@ public class LockTest extends AbstractJC
 
     /**
      * Tests the utility {@link org.apache.jackrabbit.util.Locked} by
-     * implementing running multiple threads concurrently that apply changes
-     * to a lockable node.
+     * implementing running multiple threads concurrently that apply changes to
+     * a lockable node.
      */
     public void testLockUtility() throws RepositoryException {
         final Node lockable = testRootNode.addNode(nodeName1);
         lockable.addMixin(mixLockable);
-        testRootNode.save();
+        superuser.save();
 
-        final List worker = new ArrayList();
+        final List<Thread> worker = new ArrayList<Thread>();
         for (int i = 0; i < NUM_THREADS; i++) {
             worker.add(new Thread() {
 
                 private final int threadNumber = worker.size();
 
                 public void run() {
-                    Session s;
+                    final Session s;
                     try {
                         s = getHelper().getSuperuserSession();
                     } catch (RepositoryException e) {
+                        fail(e.getMessage());
                         return;
                     }
                     try {
                         for (int i = 0; i < NUM_CHANGES; i++) {
                             Node n = (Node) s.getItem(lockable.getPath());
                             new Locked() {
-                                protected Object run(Node n) throws RepositoryException {
+                                protected Object run(Node n)
+                                        throws RepositoryException {
                                     String nodeName = "node" + threadNumber;
                                     if (n.hasNode(nodeName)) {
                                         n.getNode(nodeName).remove();
                                     } else {
                                         n.addNode(nodeName);
                                     }
-                                    n.save();
-                                    log.println("Thread" + threadNumber + ": saved modification");
+                                    s.save();
+                                    log.println("Thread" + threadNumber
+                                            + ": saved modification");
 
                                     return null;
                                 }
@@ -83,9 +87,11 @@ public class LockTest extends AbstractJC
                             Thread.sleep(new Random().nextInt(100));
                         }
                     } catch (RepositoryException e) {
-                        log.println("exception while running code with lock:" + e.getMessage());
+                        log.println("exception while running code with lock:"
+                                + e.getMessage());
                     } catch (InterruptedException e) {
-                        log.println(Thread.currentThread() + " interrupted while waiting for lock");
+                        log.println(Thread.currentThread()
+                                + " interrupted while waiting for lock");
                     } finally {
                         s.logout();
                     }
@@ -93,13 +99,13 @@ public class LockTest extends AbstractJC
             });
         }
 
-        for (Iterator it = worker.iterator(); it.hasNext(); ) {
-            ((Thread) it.next()).start();
+        for (Iterator<Thread> it = worker.iterator(); it.hasNext();) {
+            it.next().start();
         }
 
-        for (Iterator it = worker.iterator(); it.hasNext(); ) {
+        for (Iterator<Thread> it = worker.iterator(); it.hasNext();) {
             try {
-                ((Thread) it.next()).join();
+                it.next().join();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
@@ -114,41 +120,46 @@ public class LockTest extends AbstractJC
         final Node counter = testRootNode.addNode(nodeName1);
         counter.setProperty("value", 0);
         counter.addMixin(mixLockable);
-        testRootNode.save();
+        superuser.save();
 
-        final List worker = new ArrayList();
+        final List<Thread> worker = new ArrayList<Thread>();
         for (int i = 0; i < NUM_THREADS; i++) {
             worker.add(new Thread() {
 
                 private final int threadNumber = worker.size();
 
                 public void run() {
-                    Session s;
+                    final Session s;
                     try {
                         s = getHelper().getSuperuserSession();
                     } catch (RepositoryException e) {
+                        fail(e.getMessage());
                         return;
                     }
                     try {
                         for (int i = 0; i < NUM_VALUE_GETS; i++) {
                             Node n = (Node) s.getItem(counter.getPath());
                             long currentValue = ((Long) new Locked() {
-                                protected Object run(Node n) throws RepositoryException {
+                                protected Object run(Node n)
+                                        throws RepositoryException {
                                     Property seqProp = n.getProperty("value");
                                     long value = seqProp.getLong();
                                     seqProp.setValue(++value);
-                                    seqProp.save();
+                                    s.save();
                                     return new Long(value);
                                 }
                             }.with(n, false)).longValue();
-                            log.println("Thread" + threadNumber + ": got sequence number: " + currentValue);
+                            log.println("Thread" + threadNumber
+                                    + ": got sequence number: " + currentValue);
                             // do a random wait
                             Thread.sleep(new Random().nextInt(100));
                         }
                     } catch (RepositoryException e) {
-                        log.println("exception while running code with lock:" + e.getMessage());
+                        log.println("exception while running code with lock:"
+                                + e.getMessage());
                     } catch (InterruptedException e) {
-                        log.println(Thread.currentThread() + " interrupted while waiting for lock");
+                        log.println(Thread.currentThread()
+                                + " interrupted while waiting for lock");
                     } finally {
                         s.logout();
                     }
@@ -156,13 +167,13 @@ public class LockTest extends AbstractJC
             });
         }
 
-        for (Iterator it = worker.iterator(); it.hasNext(); ) {
-            ((Thread) it.next()).start();
+        for (Iterator<Thread> it = worker.iterator(); it.hasNext();) {
+            it.next().start();
         }
 
-        for (Iterator it = worker.iterator(); it.hasNext(); ) {
+        for (Iterator<Thread> it = worker.iterator(); it.hasNext();) {
             try {
-                ((Thread) it.next()).join();
+                it.next().join();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
@@ -180,46 +191,55 @@ public class LockTest extends AbstractJC
         final Node counter = testRootNode.addNode(nodeName1);
         counter.setProperty("value", 0);
         counter.addMixin(mixLockable);
-        testRootNode.save();
+        superuser.save();
 
-        final List worker = new ArrayList();
+        final List<Thread> worker = new ArrayList<Thread>();
         for (int i = 0; i < NUM_THREADS; i++) {
             worker.add(new Thread() {
 
                 private final int threadNumber = worker.size();
 
                 public void run() {
-                    Session s;
+                    final Session s;
                     try {
                         s = getHelper().getSuperuserSession();
                     } catch (RepositoryException e) {
+                        fail(e.getMessage());
                         return;
                     }
                     try {
                         for (int i = 0; i < NUM_VALUE_GETS; i++) {
                             Node n = (Node) s.getItem(counter.getPath());
                             Object ret = new Locked() {
-                                protected Object run(Node n) throws RepositoryException {
+                                protected Object run(Node n)
+                                        throws RepositoryException {
                                     Property seqProp = n.getProperty("value");
                                     long value = seqProp.getLong();
                                     seqProp.setValue(++value);
-                                    seqProp.save();
+                                    s.save();
                                     return new Long(value);
                                 }
-                            }.with(n, false, 10 * 1000); // expect a value after ten seconds
+                            }.with(n, false, 10 * 1000); // expect a value after
+                                                         // ten seconds
                             if (ret == Locked.TIMED_OUT) {
-                                log.println("Thread" + threadNumber + ": could not get a sequence number within 10 seconds");
+                                log.println("Thread"
+                                        + threadNumber
+                                        + ": could not get a sequence number within 10 seconds");
                             } else {
                                 long currentValue = ((Long) ret).longValue();
-                                log.println("Thread" + threadNumber + ": got sequence number: " + currentValue);
+                                log.println("Thread" + threadNumber
+                                        + ": got sequence number: "
+                                        + currentValue);
                             }
                             // do a random wait
                             Thread.sleep(new Random().nextInt(100));
                         }
                     } catch (RepositoryException e) {
-                        log.println("exception while running code with lock:" + e.getMessage());
+                        log.println("exception while running code with lock:"
+                                + e.getMessage());
                     } catch (InterruptedException e) {
-                        log.println(Thread.currentThread() + " interrupted while waiting for lock");
+                        log.println(Thread.currentThread()
+                                + " interrupted while waiting for lock");
                     } finally {
                         s.logout();
                     }
@@ -227,13 +247,13 @@ public class LockTest extends AbstractJC
             });
         }
 
-        for (Iterator it = worker.iterator(); it.hasNext(); ) {
-            ((Thread) it.next()).start();
+        for (Iterator<Thread> it = worker.iterator(); it.hasNext();) {
+            it.next().start();
         }
 
-        for (Iterator it = worker.iterator(); it.hasNext(); ) {
+        for (Iterator<Thread> it = worker.iterator(); it.hasNext();) {
             try {
-                ((Thread) it.next()).join();
+                it.next().join();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/LockedWrapperTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/LockedWrapperTest.java?rev=1077966&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/LockedWrapperTest.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/LockedWrapperTest.java Fri Mar  4 14:29:09 2011
@@ -0,0 +1,335 @@
+/*
+ * 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.core;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.lock.LockException;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.util.LockedWrapper;
+
+/**
+ * tests for the utility class {@link org.apache.jackrabbit.util.LockedWrapper}.
+ */
+public class LockedWrapperTest extends AbstractJCRTest {
+
+    private static final int NUM_THREADS = 100;
+
+    private static final int NUM_CHANGES = 10;
+
+    private static final int NUM_VALUE_GETS = 10;
+
+    private final Random random = new Random();
+
+    private AtomicInteger counter = new AtomicInteger(0);
+
+    /**
+     * Tests the utility {@link org.apache.jackrabbit.util.Locked} by
+     * implementing running multiple threads concurrently that apply changes to
+     * a lockable node.
+     */
+    public void testConcurrentUpdates() throws RepositoryException,
+            InterruptedException {
+
+        final Node lockable = testRootNode.addNode(nodeName1);
+        lockable.addMixin(mixLockable);
+        superuser.save();
+
+        List<Future<?>> futures = new ArrayList<Future<?>>();
+        ExecutorService e = Executors.newFixedThreadPool(NUM_THREADS);
+        for (int i = 0; i < NUM_THREADS; i++) {
+            futures.add(e.submit(buildNewConcurrentUpdateCallable(i,
+                    lockable.getPath(), false)));
+        }
+        e.shutdown();
+        assertTrue(e.awaitTermination(10 * 60, TimeUnit.SECONDS));
+
+        for (Future<?> future : futures) {
+            try {
+                future.get();
+            } catch (ExecutionException ex) {
+                fail(ex.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Tests the utility {@link org.apache.jackrabbit.util.Locked} by
+     * implementing running multiple threads concurrently that apply changes to
+     * a lockable node, also refreshing the session on each operation
+     */
+    public void testConcurrentUpdatesWithSessionRefresh()
+            throws RepositoryException, InterruptedException {
+
+        final Node lockable = testRootNode.addNode(nodeName1);
+        lockable.addMixin(mixLockable);
+        superuser.save();
+
+        List<Future<?>> futures = new ArrayList<Future<?>>();
+        ExecutorService e = Executors.newFixedThreadPool(NUM_THREADS);
+        for (int i = 0; i < NUM_THREADS; i++) {
+            futures.add(e.submit(buildNewConcurrentUpdateCallable(i,
+                    lockable.getPath(), true)));
+        }
+        e.shutdown();
+        assertTrue(e.awaitTermination(10 * 60, TimeUnit.SECONDS));
+
+        for (Future<?> future : futures) {
+            try {
+                future.get();
+            } catch (ExecutionException ex) {
+                fail(ex.getMessage());
+            }
+        }
+    }
+
+    public Callable<Void> buildNewConcurrentUpdateCallable(
+            final int threadNumber, final String lockablePath,
+            final boolean withSessionRefresh) {
+
+        return new Callable<Void>() {
+
+            public Void call() throws RepositoryException, InterruptedException {
+
+                final Session s = getHelper().getSuperuserSession();
+
+                try {
+
+                    for (int i = 0; i < NUM_CHANGES; i++) {
+
+                        if (withSessionRefresh) {
+                            s.refresh(false);
+                        }
+                        Node n = s.getNode(lockablePath);
+
+                        new LockedWrapper<Void>() {
+                            @Override
+                            protected Void run(Node node)
+                                    throws RepositoryException {
+                                String nodeName = "node" + threadNumber;
+                                if (node.hasNode(nodeName)) {
+                                    node.getNode(nodeName).remove();
+                                } else {
+                                    node.addNode(nodeName);
+                                }
+                                s.save();
+                                log.println("Thread" + threadNumber
+                                        + ": saved modification");
+                                return null;
+                            }
+                        }.with(n, false);
+
+                        // do a random wait
+                        Thread.sleep(random.nextInt(100));
+                    }
+                } finally {
+                    s.logout();
+                }
+                return null;
+            }
+
+        };
+    }
+
+    public void testTimeout() throws RepositoryException, InterruptedException {
+
+        final Node lockable = testRootNode.addNode("testTimeout"
+                + System.currentTimeMillis());
+        lockable.addMixin(mixLockable);
+        superuser.save();
+
+        ExecutorService e = Executors.newFixedThreadPool(2);
+        Future<?> lockMaster = e.submit(buildNewTimeoutCallable(
+                lockable.getPath(), true));
+        Future<?> lockSlave = e.submit(buildNewTimeoutCallable(
+                lockable.getPath(), false));
+        e.shutdown();
+
+        try {
+            lockMaster.get();
+            lockSlave.get();
+        } catch (ExecutionException ex) {
+            if (ex.getCause().getClass().isAssignableFrom(LockException.class)) {
+                return;
+            }
+            fail(ex.getMessage());
+        }
+        fail("was expecting a LockException");
+    }
+
+    public Callable<Void> buildNewTimeoutCallable(final String lockablePath,
+            final boolean isLockMaster) {
+
+        return new Callable<Void>() {
+
+            public Void call() throws RepositoryException, InterruptedException {
+
+                // allow master to be first to obtain the lock on the node
+                if (!isLockMaster) {
+                    TimeUnit.SECONDS.sleep(2);
+                }
+
+                final Session s = getHelper().getSuperuserSession();
+
+                try {
+                    Node n = s.getNode(lockablePath);
+
+                    new LockedWrapper<Void>() {
+
+                        @Override
+                        protected Void run(Node node)
+                                throws RepositoryException {
+
+                            if (isLockMaster) {
+                                try {
+                                    TimeUnit.SECONDS.sleep(15);
+                                } catch (InterruptedException e) {
+                                    //
+                                }
+                            }
+                            return null;
+                        }
+                    }.with(n, false, 2000);
+
+                } finally {
+                    s.logout();
+                }
+                return null;
+            }
+        };
+    }
+
+    /**
+     * Tests concurrent updates on a persistent counter
+     */
+    public void testSequence() throws RepositoryException, InterruptedException {
+
+        counter = new AtomicInteger(0);
+
+        final Node lockable = testRootNode.addNode(nodeName1);
+        lockable.setProperty("value", counter.getAndIncrement());
+        lockable.addMixin(mixLockable);
+        superuser.save();
+
+        List<Future<Long>> futures = new ArrayList<Future<Long>>();
+        ExecutorService e = Executors.newFixedThreadPool(NUM_THREADS);
+        for (int i = 0; i < NUM_THREADS * NUM_VALUE_GETS; i++) {
+            futures.add(e.submit(buildNewSequenceUpdateCallable(
+                    lockable.getPath(), false)));
+        }
+        e.shutdown();
+        assertTrue(e.awaitTermination(10 * 60, TimeUnit.SECONDS));
+
+        for (Future<Long> future : futures) {
+            try {
+                Long v = future.get();
+                log.println("Got sequence number: " + v);
+            } catch (ExecutionException ex) {
+                fail(ex.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Tests concurrent updates on a persistent counter, with session refresh
+     */
+    public void testSequenceWithSessionRefresh() throws RepositoryException,
+            InterruptedException {
+
+        counter = new AtomicInteger(0);
+
+        final Node lockable = testRootNode.addNode(nodeName1);
+        lockable.setProperty("value", counter.getAndIncrement());
+        lockable.addMixin(mixLockable);
+        superuser.save();
+
+        List<Future<Long>> futures = new ArrayList<Future<Long>>();
+        ExecutorService e = Executors.newFixedThreadPool(NUM_THREADS);
+        for (int i = 0; i < NUM_THREADS * NUM_VALUE_GETS; i++) {
+            futures.add(e.submit(buildNewSequenceUpdateCallable(
+                    lockable.getPath(), true)));
+        }
+        e.shutdown();
+        assertTrue(e.awaitTermination(10 * 60, TimeUnit.SECONDS));
+
+        for (Future<Long> future : futures) {
+            try {
+                Long v = future.get();
+                log.println("Got sequence number: " + v);
+            } catch (ExecutionException ex) {
+                fail(ex.getMessage());
+            }
+        }
+    }
+
+    public Callable<Long> buildNewSequenceUpdateCallable(
+            final String counterPath, final boolean withSessionRefresh) {
+
+        return new Callable<Long>() {
+
+            public Long call() throws RepositoryException, InterruptedException {
+
+                final Session s = getHelper().getSuperuserSession();
+
+                try {
+                    if (withSessionRefresh) {
+                        s.refresh(false);
+                    }
+                    Node n = s.getNode(counterPath);
+
+                    long value = new LockedWrapper<Long>() {
+
+                        @Override
+                        protected Long run(Node node)
+                                throws RepositoryException {
+
+                            Property seqProp = node.getProperty("value");
+                            long value = seqProp.getLong();
+                            seqProp.setValue(++value);
+                            s.save();
+                            return value;
+                        }
+                    }.with(n, false);
+
+                    // check that the sequence is ok
+                    assertEquals(counter.getAndIncrement(), value);
+
+                    // do a random wait
+                    Thread.sleep(random.nextInt(100));
+
+                    return value;
+                } finally {
+                    s.logout();
+                }
+            }
+        };
+    }
+}

Added: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/LockedWrapper.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/LockedWrapper.java?rev=1077966&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/LockedWrapper.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/LockedWrapper.java Fri Mar  4 14:29:09 2011
@@ -0,0 +1,107 @@
+/*
+ * 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.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.lock.LockException;
+
+/**
+ * <code>LockedWrapper</code> is a wrapper class to {@link Locked} which adds
+ * generics support and wraps the <code>Locked.TIMED_OUT</code> object into a
+ * {@link LockException}.
+ */
+public abstract class LockedWrapper<T> extends Locked {
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.jackrabbit.util.Locked#with(javax.jcr.Node, boolean)
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public T with(Node lockable, boolean isDeep) throws RepositoryException,
+            InterruptedException {
+        return (T) super.with(lockable, isDeep);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.jackrabbit.util.Locked#with(javax.jcr.Node, boolean,
+     * boolean)
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public T with(Node lockable, boolean isDeep, boolean isSessionScoped)
+            throws RepositoryException, InterruptedException {
+        return (T) super.with(lockable, isDeep, isSessionScoped);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.jackrabbit.util.Locked#with(javax.jcr.Node, boolean,
+     * long)
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public T with(Node lockable, boolean isDeep, long timeout)
+            throws UnsupportedRepositoryOperationException,
+            RepositoryException, InterruptedException {
+
+        Object r = super.with(lockable, isDeep, timeout);
+        if (r == Locked.TIMED_OUT) {
+            throw new LockException("Node locked.");
+        }
+        return (T) r;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.apache.jackrabbit.util.Locked#with(javax.jcr.Node, boolean,
+     * long, boolean)
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public Object with(Node lockable, boolean isDeep, long timeout,
+            boolean isSessionScoped)
+            throws UnsupportedRepositoryOperationException,
+            RepositoryException, InterruptedException {
+
+        Object r = super.with(lockable, isDeep, timeout, isSessionScoped);
+        if (r == Locked.TIMED_OUT) {
+            throw new LockException("Node locked.");
+        }
+        return (T) r;
+    }
+
+    /**
+     * This method is executed while holding the lock.
+     * 
+     * @param node
+     *            The <code>Node</code> on which the lock is placed.
+     * @return an object which is then returned by {@link #with with()}.
+     * @throws RepositoryException
+     *             if an error occurs.
+     */
+    @Override
+    protected abstract T run(Node node) throws RepositoryException;
+
+}
\ No newline at end of file