From 93ef366125f1d2e72c96c43cda42af13833cb9f8 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 12 Feb 2016 01:01:26 +0100 Subject: [PATCH] now passing 94.6% of litmus lock tests --- .../filesystem/jackrabbit/FileLocator.java | 2 +- .../jackrabbit/FileSystemResourceLocator.java | 11 -- .../filesystem/jackrabbit/FolderLocator.java | 2 +- .../InternalFileSystemResourceLocator.java | 21 ++ .../webdav/jackrabbitservlet/DavNode.java | 18 +- .../ExclusiveSharedLock.java | 101 ++++++++++ .../ExclusiveSharedLockManager.java | 183 ++++++++++++++++++ .../FilesystemResourceFactory.java | 3 +- .../UncheckedDavException.java | 22 +++ .../jackrabbitservlet/WebDavServlet.java | 24 ++- 10 files changed, 350 insertions(+), 37 deletions(-) create mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/InternalFileSystemResourceLocator.java create mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/ExclusiveSharedLock.java create mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/ExclusiveSharedLockManager.java create mode 100644 main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/UncheckedDavException.java diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileLocator.java b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileLocator.java index e5222b8cf..01846b47e 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileLocator.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileLocator.java @@ -14,7 +14,7 @@ import org.apache.jackrabbit.webdav.DavLocatorFactory; import org.cryptomator.filesystem.File; import org.cryptomator.filesystem.delegating.DelegatingFile; -public class FileLocator extends DelegatingFileimplements FileSystemResourceLocator { +public class FileLocator extends DelegatingFileimplements InternalFileSystemResourceLocator { private final DavLocatorFactory factory; private final String prefix; diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocator.java b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocator.java index 0b0a29789..0ee442644 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocator.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FileSystemResourceLocator.java @@ -9,24 +9,13 @@ package org.cryptomator.filesystem.jackrabbit; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; import org.apache.jackrabbit.webdav.DavResourceLocator; import org.apache.jackrabbit.webdav.util.EncodeUtil; -import org.cryptomator.common.LazyInitializer; import org.cryptomator.filesystem.Node; public interface FileSystemResourceLocator extends DavResourceLocator, Node { - @Override - default String getResourcePath() { - return LazyInitializer.initializeLazily(getResourcePathRef(), this::computeResourcePath); - } - - AtomicReference getResourcePathRef(); - - String computeResourcePath(); - @Override Optional parent(); diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FolderLocator.java b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FolderLocator.java index 015bcefee..e1532d7f5 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FolderLocator.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/FolderLocator.java @@ -16,7 +16,7 @@ import org.cryptomator.filesystem.File; import org.cryptomator.filesystem.Folder; import org.cryptomator.filesystem.delegating.DelegatingFolder; -public class FolderLocator extends DelegatingFolderimplements FileSystemResourceLocator { +public class FolderLocator extends DelegatingFolderimplements InternalFileSystemResourceLocator { private final DavLocatorFactory factory; private final String prefix; diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/InternalFileSystemResourceLocator.java b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/InternalFileSystemResourceLocator.java new file mode 100644 index 000000000..d2be89cc2 --- /dev/null +++ b/main/frontend-webdav/src/main/java/org/cryptomator/filesystem/jackrabbit/InternalFileSystemResourceLocator.java @@ -0,0 +1,21 @@ +package org.cryptomator.filesystem.jackrabbit; + +import java.util.concurrent.atomic.AtomicReference; + +import org.cryptomator.common.LazyInitializer; + +/** + * Adds package-private API to {@link FileSystemResourceLocator}. + */ +interface InternalFileSystemResourceLocator extends FileSystemResourceLocator { + + @Override + default String getResourcePath() { + return LazyInitializer.initializeLazily(getResourcePathRef(), this::computeResourcePath); + } + + AtomicReference getResourcePathRef(); + + String computeResourcePath(); + +} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/DavNode.java b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/DavNode.java index 4aee0f277..240810f03 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/DavNode.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/DavNode.java @@ -16,12 +16,12 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.Temporal; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Optional; +import java.util.stream.Stream; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.DavResource; -import org.apache.jackrabbit.webdav.DavResourceLocator; -import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.DavSession; import org.apache.jackrabbit.webdav.MultiStatusResponse; import org.apache.jackrabbit.webdav.lock.ActiveLock; @@ -78,7 +78,7 @@ abstract class DavNode implements DavResour } @Override - public DavResourceLocator getLocator() { + public FileSystemResourceLocator getLocator() { return node; } @@ -202,7 +202,7 @@ abstract class DavNode implements DavResour @Override public boolean isLockable(Type type, Scope scope) { - return true; + return Type.WRITE.equals(type) && Scope.EXCLUSIVE.equals(scope) || Scope.SHARED.equals(scope); } @Override @@ -218,18 +218,12 @@ abstract class DavNode implements DavResour @Override public ActiveLock[] getLocks() { final ActiveLock exclusiveWriteLock = getLock(Type.WRITE, Scope.EXCLUSIVE); - if (exclusiveWriteLock != null) { - return new ActiveLock[] {exclusiveWriteLock}; - } else { - return new ActiveLock[0]; - } + final ActiveLock sharedWriteLock = getLock(Type.WRITE, Scope.SHARED); + return Stream.of(exclusiveWriteLock, sharedWriteLock).filter(Objects::nonNull).toArray(ActiveLock[]::new); } @Override public ActiveLock lock(LockInfo reqLockInfo) throws DavException { - if (Scope.SHARED.equals(reqLockInfo.getScope())) { - throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED, "Only exclusive write locks supported."); - } return lockManager.createLock(reqLockInfo, this); } diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/ExclusiveSharedLock.java b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/ExclusiveSharedLock.java new file mode 100644 index 000000000..f0a252f37 --- /dev/null +++ b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/ExclusiveSharedLock.java @@ -0,0 +1,101 @@ +package org.cryptomator.webdav.jackrabbitservlet; + +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.lock.AbstractActiveLock; +import org.apache.jackrabbit.webdav.lock.LockInfo; +import org.apache.jackrabbit.webdav.lock.Scope; +import org.apache.jackrabbit.webdav.lock.Type; + +public class ExclusiveSharedLock extends AbstractActiveLock { + + private final String token; + private final Type type; + private final Scope scope; + private String owner; + private boolean isDeep = true; // deep by default + private long expirationTime = DavConstants.INFINITE_TIMEOUT; // never expires by default; + + ExclusiveSharedLock(String token, LockInfo lockInfo) { + this.token = token; + this.type = lockInfo.getType(); + this.scope = lockInfo.getScope(); + this.owner = lockInfo.getOwner(); + this.isDeep = lockInfo.isDeep(); + setTimeout(lockInfo.getTimeout()); + } + + @Override + public boolean isLockedByToken(String lockToken) { + return token.equals(lockToken); + } + + @Override + public boolean isExpired() { + return System.currentTimeMillis() > expirationTime; + } + + @Override + public String getToken() { + return token; + } + + @Override + public String getOwner() { + return owner; + } + + @Override + public void setOwner(String owner) { + this.owner = owner; + } + + @Override + public long getTimeout() { + return expirationTime - System.currentTimeMillis(); + } + + @Override + public void setTimeout(long timeout) { + if (timeout > 0) { + expirationTime = System.currentTimeMillis() + timeout; + } + } + + @Override + public boolean isDeep() { + return isDeep; + } + + @Override + public void setIsDeep(boolean isDeep) { + this.isDeep = isDeep; + } + + @Override + public Type getType() { + return type; + } + + @Override + public Scope getScope() { + return scope; + } + + /* HASHCODE / EQUALS */ + + @Override + public int hashCode() { + return getToken().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ExclusiveSharedLock) { + ExclusiveSharedLock other = (ExclusiveSharedLock) obj; + return this.getToken().equals(other.getToken()); + } else { + return false; + } + } + +} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/ExclusiveSharedLockManager.java b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/ExclusiveSharedLockManager.java new file mode 100644 index 000000000..828820ab5 --- /dev/null +++ b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/ExclusiveSharedLockManager.java @@ -0,0 +1,183 @@ +package org.cryptomator.webdav.jackrabbitservlet; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.DavResource; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.lock.ActiveLock; +import org.apache.jackrabbit.webdav.lock.LockInfo; +import org.apache.jackrabbit.webdav.lock.LockManager; +import org.apache.jackrabbit.webdav.lock.Scope; +import org.apache.jackrabbit.webdav.lock.Type; +import org.cryptomator.filesystem.jackrabbit.FileSystemResourceLocator; +import org.cryptomator.filesystem.jackrabbit.FolderLocator; + +public class ExclusiveSharedLockManager implements LockManager { + + private final ConcurrentMap> lockedResources = new ConcurrentHashMap<>(); + + @Override + public ActiveLock createLock(LockInfo lockInfo, DavResource resource) throws DavException { + Objects.requireNonNull(lockInfo); + Objects.requireNonNull(resource); + if (resource instanceof DavNode) { + return createLockInternal(lockInfo, (DavNode) resource); + } else { + throw new IllegalArgumentException("Unsupported resource type " + resource.getClass()); + } + } + + private synchronized ActiveLock createLockInternal(LockInfo lockInfo, DavNode resource) throws DavException { + FileSystemResourceLocator locator = resource.getLocator(); + removedExpiredLocksInLocatorHierarchy(locator); + + ActiveLock existingExclusiveLock = getLock(lockInfo.getType(), Scope.EXCLUSIVE, resource); + ActiveLock existingSharedLock = getLock(lockInfo.getType(), Scope.SHARED, resource); + boolean hasExclusiveLock = existingExclusiveLock != null; + boolean hasSharedLock = existingSharedLock != null; + boolean isLocked = hasExclusiveLock || hasSharedLock; + if ((Scope.EXCLUSIVE.equals(lockInfo.getScope()) && isLocked) || (Scope.SHARED.equals(lockInfo.getScope()) && hasExclusiveLock)) { + throw new DavException(DavServletResponse.SC_LOCKED, "Resource already locked."); + } + + for (Entry> entry : lockedResources.entrySet()) { + final FileSystemResourceLocator entryLocator = entry.getKey(); + final Collection entryLocks = entry.getValue().values(); + if (isAncestor(entryLocator, locator) && isAffectedByParentLocks(lockInfo, locator, entryLocks, entryLocator)) { + throw new DavException(DavServletResponse.SC_LOCKED, "Parent resource already locked. " + entryLocator); + } else if (isAncestor(locator, entryLocator) && isAffectedByChildLocks(lockInfo, locator, entryLocks, entryLocator)) { + throw new DavException(DavServletResponse.SC_CONFLICT, "Subresource already locked. " + entryLocator); + } + } + + String token = DavConstants.OPAQUE_LOCK_TOKEN_PREFIX + UUID.randomUUID(); + return lockedResources.computeIfAbsent(locator, loc -> new HashMap<>()).computeIfAbsent(token, t -> new ExclusiveSharedLock(t, lockInfo)); + } + + private void removedExpiredLocksInLocatorHierarchy(FileSystemResourceLocator locator) { + lockedResources.getOrDefault(locator, Collections.emptyMap()).values().removeIf(ActiveLock::isExpired); + locator.parent().ifPresent(this::removedExpiredLocksInLocatorHierarchy); + } + + private boolean isAncestor(FileSystemResourceLocator parent, FileSystemResourceLocator child) { + if (parent instanceof FolderLocator) { + FolderLocator folder = (FolderLocator) parent; + return folder.isAncestorOf(child); + } else { + return false; + } + } + + private boolean isAffectedByParentLocks(LockInfo childLockInfo, FileSystemResourceLocator childLocator, Collection parentLocks, FileSystemResourceLocator parentLocator) { + assert childLocator.parent().isPresent(); + for (ActiveLock lock : parentLocks) { + if (Scope.SHARED.equals(childLockInfo.getScope()) && Scope.SHARED.equals(lock.getScope())) { + continue; + } else if (lock.isDeep() || childLocator.parent().get().equals(parentLocator)) { + return true; + } + } + return false; + } + + private boolean isAffectedByChildLocks(LockInfo parentLockInfo, FileSystemResourceLocator parentLocator, Collection childLocks, FileSystemResourceLocator childLocator) { + assert childLocator.parent().isPresent(); + for (ActiveLock lock : childLocks) { + if (Scope.SHARED.equals(lock.getScope()) && Scope.SHARED.equals(parentLockInfo.getScope())) { + continue; + } else if (parentLockInfo.isDeep() || childLocator.parent().get().equals(parentLocator)) { + return true; + } + } + return false; + } + + @Override + public ActiveLock refreshLock(LockInfo lockInfo, String lockToken, DavResource resource) throws DavException { + ActiveLock lock = getLock(lockInfo.getType(), lockInfo.getScope(), resource); + if (lock == null) { + throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED); + } else if (!lock.getToken().equals(lockToken)) { + throw new DavException(DavServletResponse.SC_LOCKED); + } + lock.setTimeout(lockInfo.getTimeout()); + return lock; + } + + @Override + public synchronized void releaseLock(String lockToken, DavResource resource) throws DavException { + if (resource instanceof DavNode) { + try { + releaseLockInternal(lockToken, (DavNode) resource); + } catch (UncheckedDavException e) { + throw e.toDavException(); + } + } else { + throw new IllegalArgumentException("Unsupported resource type " + resource.getClass()); + } + } + + private synchronized void releaseLockInternal(String lockToken, DavNode resource) throws UncheckedDavException { + lockedResources.compute(resource.getLocator(), (loc, locks) -> { + if (locks == null || locks.isEmpty()) { + // no lock exists, nothing needs to change. + return null; + } else if (!locks.containsKey(lockToken)) { + throw new UncheckedDavException(DavServletResponse.SC_LOCKED, "Resource locked with different token."); + } else { + locks.remove(lockToken); + return locks.isEmpty() ? null : locks; + } + }); + } + + @Override + public ActiveLock getLock(Type type, Scope scope, DavResource resource) { + if (resource instanceof DavNode) { + return getLockInternal(type, scope, ((DavNode) resource).getLocator()); + } else { + throw new IllegalArgumentException("Unsupported resource type " + resource.getClass()); + } + } + + private ActiveLock getLockInternal(Type type, Scope scope, FileSystemResourceLocator locator) { + // try to find a lock directly on this resource: + if (lockedResources.containsKey(locator)) { + for (ActiveLock lock : lockedResources.get(locator).values()) { + if (type.equals(lock.getType()) && scope.equals(lock.getScope())) { + return lock; + } + } + } + // or otherwise look for parent locks: + if (locator.parent().isPresent()) { + return getLockInternal(type, scope, locator.parent().get()); + } else { + return null; + } + } + + @Override + public boolean hasLock(String lockToken, DavResource resource) { + if (resource instanceof DavNode) { + return hasLockInternal(lockToken, (DavNode) resource); + } else { + throw new IllegalArgumentException("Unsupported resource type " + resource.getClass()); + } + } + + private boolean hasLockInternal(String lockToken, DavNode resource) { + return lockedResources.getOrDefault(resource.getLocator(), Collections.emptyMap()).containsKey(lockToken); + } + +} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/FilesystemResourceFactory.java b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/FilesystemResourceFactory.java index ba13098d5..fbc08dad7 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/FilesystemResourceFactory.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/FilesystemResourceFactory.java @@ -16,7 +16,6 @@ import org.apache.jackrabbit.webdav.DavServletRequest; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.DavSession; import org.apache.jackrabbit.webdav.lock.LockManager; -import org.apache.jackrabbit.webdav.lock.SimpleLockManager; import org.cryptomator.filesystem.jackrabbit.FileLocator; import org.cryptomator.filesystem.jackrabbit.FolderLocator; @@ -25,7 +24,7 @@ class FilesystemResourceFactory implements DavResourceFactory { private final LockManager lockManager; public FilesystemResourceFactory() { - this.lockManager = new SimpleLockManager(); + this.lockManager = new ExclusiveSharedLockManager(); } @Override diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/UncheckedDavException.java b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/UncheckedDavException.java new file mode 100644 index 000000000..23cf01f16 --- /dev/null +++ b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/UncheckedDavException.java @@ -0,0 +1,22 @@ +package org.cryptomator.webdav.jackrabbitservlet; + +import org.apache.jackrabbit.webdav.DavException; + +public class UncheckedDavException extends RuntimeException { + + private final int errorCode; + + public UncheckedDavException(int errorCode, String message) { + this(errorCode, message, null); + } + + public UncheckedDavException(int errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = errorCode; + } + + public DavException toDavException() { + return new DavException(errorCode, getMessage(), getCause(), null); + } + +} diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/WebDavServlet.java b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/WebDavServlet.java index 2b1a840a8..6fa543e2f 100644 --- a/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/WebDavServlet.java +++ b/main/frontend-webdav/src/main/java/org/cryptomator/webdav/jackrabbitservlet/WebDavServlet.java @@ -78,42 +78,46 @@ public class WebDavServlet extends AbstractWebdavServlet { @Override protected int validateDestination(DavResource destResource, WebdavRequest request, boolean checkHeader) throws DavException { - if (destResource.hasLock(Type.WRITE, Scope.EXCLUSIVE) && (request.getHeader(HEADER_IF) == null || !request.matchesIfHeader(destResource))) { - throw new DavException(DavServletResponse.SC_LOCKED, "The destination resource was locked"); + if (isLocked(destResource) && (request.getHeader(HEADER_IF) == null || !request.matchesIfHeader(destResource))) { + throw new DavException(DavServletResponse.SC_LOCKED, "The destination resource is locked"); } return super.validateDestination(destResource, request, checkHeader); } @Override protected void doPut(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { - if (resource.hasLock(Type.WRITE, Scope.EXCLUSIVE) && (request.getHeader(HEADER_IF) == null || !request.matchesIfHeader(resource))) { - throw new DavException(DavServletResponse.SC_LOCKED, "The resource was locked"); + if (isLocked(resource) && (request.getHeader(HEADER_IF) == null || !request.matchesIfHeader(resource))) { + throw new DavException(DavServletResponse.SC_LOCKED, "The resource is locked"); } super.doPut(request, response, resource); } @Override protected void doDelete(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { - if (resource.hasLock(Type.WRITE, Scope.EXCLUSIVE) && (request.getHeader(HEADER_IF) == null || !request.matchesIfHeader(resource))) { - throw new DavException(DavServletResponse.SC_LOCKED, "The resource was locked"); + if (isLocked(resource) && (request.getHeader(HEADER_IF) == null || !request.matchesIfHeader(resource))) { + throw new DavException(DavServletResponse.SC_LOCKED, "The resource is locked"); } super.doDelete(request, response, resource); } @Override protected void doMove(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { - if (resource.hasLock(Type.WRITE, Scope.EXCLUSIVE) && (request.getHeader(HEADER_IF) == null || !request.matchesIfHeader(resource))) { - throw new DavException(DavServletResponse.SC_LOCKED, "The source resource was locked"); + if (isLocked(resource) && (request.getHeader(HEADER_IF) == null || !request.matchesIfHeader(resource))) { + throw new DavException(DavServletResponse.SC_LOCKED, "The source resource is locked"); } super.doMove(request, response, resource); } @Override protected void doPropPatch(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { - if (resource.hasLock(Type.WRITE, Scope.EXCLUSIVE) && (request.getHeader(HEADER_IF) == null || !request.matchesIfHeader(resource))) { - throw new DavException(DavServletResponse.SC_LOCKED, "The resource was locked"); + if (isLocked(resource) && (request.getHeader(HEADER_IF) == null || !request.matchesIfHeader(resource))) { + throw new DavException(DavServletResponse.SC_LOCKED, "The resource is locked"); } super.doPropPatch(request, response, resource); } + private boolean isLocked(DavResource resource) { + return resource.hasLock(Type.WRITE, Scope.EXCLUSIVE) || resource.hasLock(Type.WRITE, Scope.SHARED); + } + }