now passing 94.6% of litmus lock tests

This commit is contained in:
Sebastian Stenzel
2016-02-12 01:01:26 +01:00
parent cbcefc4eb5
commit 93ef366125
10 changed files with 350 additions and 37 deletions

View File

@@ -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 DelegatingFile<FolderLocator>implements FileSystemResourceLocator {
public class FileLocator extends DelegatingFile<FolderLocator>implements InternalFileSystemResourceLocator {
private final DavLocatorFactory factory;
private final String prefix;

View File

@@ -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<String> getResourcePathRef();
String computeResourcePath();
@Override
Optional<FolderLocator> parent();

View File

@@ -16,7 +16,7 @@ import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.delegating.DelegatingFolder;
public class FolderLocator extends DelegatingFolder<FolderLocator, FileLocator>implements FileSystemResourceLocator {
public class FolderLocator extends DelegatingFolder<FolderLocator, FileLocator>implements InternalFileSystemResourceLocator {
private final DavLocatorFactory factory;
private final String prefix;

View File

@@ -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<String> getResourcePathRef();
String computeResourcePath();
}

View File

@@ -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<T extends FileSystemResourceLocator> implements DavResour
}
@Override
public DavResourceLocator getLocator() {
public FileSystemResourceLocator getLocator() {
return node;
}
@@ -202,7 +202,7 @@ abstract class DavNode<T extends FileSystemResourceLocator> 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<T extends FileSystemResourceLocator> 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);
}

View File

@@ -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;
}
}
}

View File

@@ -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<FileSystemResourceLocator, Map<String, ActiveLock>> 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<FileSystemResourceLocator, Map<String, ActiveLock>> entry : lockedResources.entrySet()) {
final FileSystemResourceLocator entryLocator = entry.getKey();
final Collection<ActiveLock> 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<ActiveLock> 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<ActiveLock> 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);
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);
}
}