mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-04-22 18:46:53 -04:00
first experiments with jackrabbit-filesystem-adapter
This commit is contained in:
78
main/jackrabbit-filesystem-adapter/pom.xml
Normal file
78
main/jackrabbit-filesystem-adapter/pom.xml
Normal file
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2015 Sebastian Stenzel
|
||||
This file is licensed under the terms of the MIT license.
|
||||
See the LICENSE.txt file for more info.
|
||||
|
||||
Contributors:
|
||||
Sebastian Stenzel - initial API and implementation
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>jackrabbit-filesystem-adapter</artifactId>
|
||||
<name>Jackrabbit filesystem-WebDAV-adapter</name>
|
||||
<description>WebDAV servlet based on Apache Jackrabbit serving files from a filesystem</description>
|
||||
|
||||
<properties>
|
||||
<jackrabbit.version>2.11.0</jackrabbit.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Filesystem -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackrabbit -->
|
||||
<dependency>
|
||||
<groupId>org.apache.jackrabbit</groupId>
|
||||
<artifactId>jackrabbit-webdav</artifactId>
|
||||
<version>${jackrabbit.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Commons -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>9.3.3.v20150827</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>9.3.3.v20150827</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-httpclient</groupId>
|
||||
<artifactId>commons-httpclient</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-inmemory</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,98 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.webdav.jackrabbit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.apache.jackrabbit.webdav.DavException;
|
||||
import org.apache.jackrabbit.webdav.DavResource;
|
||||
import org.apache.jackrabbit.webdav.DavResourceIterator;
|
||||
import org.apache.jackrabbit.webdav.DavResourceLocator;
|
||||
import org.apache.jackrabbit.webdav.DavSession;
|
||||
import org.apache.jackrabbit.webdav.io.InputContext;
|
||||
import org.apache.jackrabbit.webdav.io.OutputContext;
|
||||
import org.apache.jackrabbit.webdav.lock.LockManager;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
|
||||
class DavFile extends DavNode<File> {
|
||||
|
||||
public DavFile(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, DavResourceLocator locator, File node) {
|
||||
super(factory, lockManager, session, locator, node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollection() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spool(OutputContext outputContext) throws IOException {
|
||||
outputContext.setModificationTime(node.lastModified().toEpochMilli());
|
||||
if (!outputContext.hasStream()) {
|
||||
return;
|
||||
}
|
||||
try (ReadableFile src = node.openReadable(); WritableByteChannel dst = Channels.newChannel(outputContext.getOutputStream())) {
|
||||
// TODO filesize before sending content
|
||||
outputContext.setContentLength(-1l);
|
||||
ByteBuffer buf = ByteBuffer.allocate(1337);
|
||||
do {
|
||||
buf.clear();
|
||||
src.read(buf);
|
||||
buf.flip();
|
||||
} while (dst.write(buf) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMember(DavResource resource, InputContext inputContext) throws DavException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResourceIterator getMembers() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeMember(DavResource member) throws DavException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void move(DavResource destination) throws DavException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy(DavResource destination, boolean shallow) throws DavException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setModificationTime(Instant instant) {
|
||||
try (WritableFile writable = node.openWritable()) {
|
||||
writable.setLastModified(instant);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setCreationTime(Instant instant) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.webdav.jackrabbit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.jackrabbit.webdav.DavException;
|
||||
import org.apache.jackrabbit.webdav.DavResource;
|
||||
import org.apache.jackrabbit.webdav.DavResourceIterator;
|
||||
import org.apache.jackrabbit.webdav.DavResourceIteratorImpl;
|
||||
import org.apache.jackrabbit.webdav.DavResourceLocator;
|
||||
import org.apache.jackrabbit.webdav.DavSession;
|
||||
import org.apache.jackrabbit.webdav.io.InputContext;
|
||||
import org.apache.jackrabbit.webdav.io.OutputContext;
|
||||
import org.apache.jackrabbit.webdav.lock.LockManager;
|
||||
import org.apache.jackrabbit.webdav.property.DavPropertyName;
|
||||
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
|
||||
import org.apache.jackrabbit.webdav.property.ResourceType;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
|
||||
class DavFolder extends DavNode<Folder> {
|
||||
|
||||
public DavFolder(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, DavResourceLocator locator, Folder folder) {
|
||||
super(factory, lockManager, session, locator, folder);
|
||||
properties.add(new ResourceType(ResourceType.COLLECTION));
|
||||
properties.add(new DefaultDavProperty<Integer>(DavPropertyName.ISCOLLECTION, 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollection() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spool(OutputContext outputContext) throws IOException {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMember(DavResource resource, InputContext inputContext) throws DavException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResourceIterator getMembers() {
|
||||
final Stream<DavFolder> folders = node.folders().map(this::getMemberFolder);
|
||||
final Stream<DavFile> files = node.files().map(this::getMemberFile);
|
||||
return new DavResourceIteratorImpl(Stream.concat(folders, files).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private DavFolder getMemberFolder(Folder memberFolder) {
|
||||
final String subFolderResourcePath = locator.getResourcePath() + memberFolder.name() + '/';
|
||||
final DavResourceLocator subFolderLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), subFolderResourcePath);
|
||||
return factory.createFolder(memberFolder, subFolderLocator, session);
|
||||
}
|
||||
|
||||
private DavFile getMemberFile(File memberFile) {
|
||||
final String subFolderResourcePath = locator.getResourcePath() + memberFile.name();
|
||||
final DavResourceLocator subFolderLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), subFolderResourcePath);
|
||||
return factory.createFile(memberFile, subFolderLocator, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeMember(DavResource member) throws DavException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void move(DavResource destination) throws DavException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy(DavResource destination, boolean shallow) throws DavException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setModificationTime(Instant instant) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setCreationTime(Instant instant) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.webdav.jackrabbit;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.jackrabbit.webdav.DavException;
|
||||
import org.apache.jackrabbit.webdav.DavResource;
|
||||
import org.apache.jackrabbit.webdav.DavResourceLocator;
|
||||
import org.apache.jackrabbit.webdav.DavSession;
|
||||
import org.apache.jackrabbit.webdav.MultiStatusResponse;
|
||||
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.apache.jackrabbit.webdav.property.DavProperty;
|
||||
import org.apache.jackrabbit.webdav.property.DavPropertyName;
|
||||
import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
|
||||
import org.apache.jackrabbit.webdav.property.DavPropertySet;
|
||||
import org.apache.jackrabbit.webdav.property.PropEntry;
|
||||
import org.cryptomator.filesystem.Node;
|
||||
|
||||
abstract class DavNode<T extends Node> implements DavResource {
|
||||
|
||||
private static final String DAV_COMPLIANCE_CLASSES = "1, 2";
|
||||
private static final String[] DAV_CREATIONDATE_PROPNAMES = {DavPropertyName.CREATIONDATE.getName(), "Win32CreationTime"};
|
||||
private static final String[] DAV_MODIFIEDDATE_PROPNAMES = {DavPropertyName.GETLASTMODIFIED.getName(), "Win32LastModifiedTime"};
|
||||
|
||||
protected final FilesystemResourceFactory factory;
|
||||
protected final LockManager lockManager;
|
||||
protected final DavSession session;
|
||||
protected final DavResourceLocator locator;
|
||||
protected final T node;
|
||||
protected final DavPropertySet properties;
|
||||
|
||||
public DavNode(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, DavResourceLocator locator, T node) {
|
||||
this.factory = factory;
|
||||
this.lockManager = lockManager;
|
||||
this.session = session;
|
||||
this.locator = locator;
|
||||
this.node = node;
|
||||
this.properties = new DavPropertySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComplianceClass() {
|
||||
return DAV_COMPLIANCE_CLASSES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSupportedMethods() {
|
||||
return METHODS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return node.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return node.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResourceLocator getLocator() {
|
||||
return locator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourcePath() {
|
||||
return locator.getResourcePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHref() {
|
||||
return locator.getHref(this.isCollection());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getModificationTime() {
|
||||
try {
|
||||
return node.lastModified().toEpochMilli();
|
||||
} catch (UncheckedIOException e) {
|
||||
return -1l;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void setModificationTime(Instant instant);
|
||||
|
||||
protected abstract void setCreationTime(Instant instant);
|
||||
|
||||
@Override
|
||||
public DavPropertyName[] getPropertyNames() {
|
||||
return getProperties().getPropertyNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavProperty<?> getProperty(DavPropertyName name) {
|
||||
return getProperties().get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavPropertySet getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(DavProperty<?> property) throws DavException {
|
||||
getProperties().add(property);
|
||||
|
||||
final String namespacelessPropertyName = property.getName().getName();
|
||||
if (Arrays.asList(DAV_CREATIONDATE_PROPNAMES).contains(namespacelessPropertyName) && property.getValue() instanceof String) {
|
||||
final String createDateStr = (String) property.getValue();
|
||||
final Instant createTime = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(createDateStr));
|
||||
this.setCreationTime(createTime);
|
||||
} else if (Arrays.asList(DAV_MODIFIEDDATE_PROPNAMES).contains(namespacelessPropertyName) && property.getValue() instanceof String) {
|
||||
final String lastModifiedTimeStr = (String) property.getValue();
|
||||
final Instant createTime = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(lastModifiedTimeStr));
|
||||
this.setCreationTime(createTime);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProperty(DavPropertyName propertyName) throws DavException {
|
||||
getProperties().remove(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiStatusResponse alterProperties(List<? extends PropEntry> changeList) throws DavException {
|
||||
final DavPropertyNameSet names = new DavPropertyNameSet();
|
||||
for (final PropEntry entry : changeList) {
|
||||
if (entry instanceof DavProperty) {
|
||||
final DavProperty<?> prop = (DavProperty<?>) entry;
|
||||
this.setProperty(prop);
|
||||
names.add(prop.getName());
|
||||
} else if (entry instanceof DavPropertyName) {
|
||||
final DavPropertyName name = (DavPropertyName) entry;
|
||||
this.removeProperty(name);
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
return new MultiStatusResponse(this, names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResource getCollection() {
|
||||
if (locator.isRootLocation()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String parentResource = FilenameUtils.getPathNoEndSeparator(locator.getResourcePath());
|
||||
final DavResourceLocator parentLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), parentResource);
|
||||
try {
|
||||
return factory.createResource(parentLocator, session);
|
||||
} catch (DavException e) {
|
||||
throw new IllegalStateException("Unable to get parent resource with path " + parentLocator.getResourcePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLockable(Type type, Scope scope) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLock(Type type, Scope scope) {
|
||||
return getLock(type, scope) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActiveLock getLock(Type type, Scope scope) {
|
||||
return lockManager.getLock(type, scope, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActiveLock[] getLocks() {
|
||||
final ActiveLock exclusiveWriteLock = getLock(Type.WRITE, Scope.EXCLUSIVE);
|
||||
if (exclusiveWriteLock != null) {
|
||||
return new ActiveLock[] {exclusiveWriteLock};
|
||||
} else {
|
||||
return new ActiveLock[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActiveLock lock(LockInfo reqLockInfo) throws DavException {
|
||||
return lockManager.createLock(reqLockInfo, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActiveLock refreshLock(LockInfo reqLockInfo, String lockToken) throws DavException {
|
||||
return lockManager.refreshLock(reqLockInfo, lockToken, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock(String lockToken) throws DavException {
|
||||
lockManager.releaseLock(lockToken, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLockManager(LockManager lockmgr) {
|
||||
throw new UnsupportedOperationException("Locks are managed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilesystemResourceFactory getFactory() {
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014, 2015 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.webdav.jackrabbit;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.apache.jackrabbit.webdav.DavSession;
|
||||
|
||||
class DavSessionImpl implements DavSession {
|
||||
|
||||
private final HashSet<String> lockTokens = new HashSet<String>();
|
||||
private final HashSet<Object> references = new HashSet<Object>();
|
||||
|
||||
@Override
|
||||
public void addReference(Object reference) {
|
||||
references.add(reference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeReference(Object reference) {
|
||||
references.remove(reference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLockToken(String token) {
|
||||
lockTokens.add(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getLockTokens() {
|
||||
return lockTokens.toArray(new String[lockTokens.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeLockToken(String token) {
|
||||
lockTokens.remove(token);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014, 2015 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.webdav.jackrabbit;
|
||||
|
||||
import org.apache.jackrabbit.webdav.DavException;
|
||||
import org.apache.jackrabbit.webdav.DavSession;
|
||||
import org.apache.jackrabbit.webdav.DavSessionProvider;
|
||||
import org.apache.jackrabbit.webdav.WebdavRequest;
|
||||
|
||||
class DavSessionProviderImpl implements DavSessionProvider {
|
||||
|
||||
@Override
|
||||
public boolean attachSession(WebdavRequest request) throws DavException {
|
||||
// every request gets a new session
|
||||
final DavSession session = new DavSessionImpl();
|
||||
session.addReference(request);
|
||||
request.setDavSession(session);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseSession(WebdavRequest request) {
|
||||
final DavSession session = request.getDavSession();
|
||||
if (session != null) {
|
||||
session.removeReference(request);
|
||||
request.setDavSession(null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.webdav.jackrabbit;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jackrabbit.webdav.DavException;
|
||||
import org.apache.jackrabbit.webdav.DavResource;
|
||||
import org.apache.jackrabbit.webdav.DavResourceFactory;
|
||||
import org.apache.jackrabbit.webdav.DavResourceLocator;
|
||||
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.File;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.Node;
|
||||
|
||||
class FilesystemResourceFactory implements DavResourceFactory {
|
||||
|
||||
private static final Class<Folder> FOLDER = Folder.class;
|
||||
private static final Class<File> FILE = File.class;
|
||||
|
||||
private final FileSystem filesystem;
|
||||
private final LockManager lockManager;
|
||||
|
||||
public FilesystemResourceFactory(FileSystem filesystem) {
|
||||
this.filesystem = filesystem;
|
||||
this.lockManager = new SimpleLockManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResource createResource(DavResourceLocator locator, DavServletRequest request, DavServletResponse response) throws DavException {
|
||||
return createResource(locator, request.getDavSession());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException {
|
||||
final String path = locator.getResourcePath();
|
||||
if (path.endsWith("/")) {
|
||||
Folder folder = this.resolve(path, FOLDER);
|
||||
return createFolder(folder, locator, session);
|
||||
} else {
|
||||
File file = this.resolve(path, FILE);
|
||||
return createFile(file, locator, session);
|
||||
}
|
||||
}
|
||||
|
||||
DavFolder createFolder(Folder folder, DavResourceLocator locator, DavSession session) {
|
||||
return new DavFolder(this, lockManager, session, locator, folder);
|
||||
}
|
||||
|
||||
public DavFile createFile(File file, DavResourceLocator locator, DavSession session) {
|
||||
return new DavFile(this, lockManager, session, locator, file);
|
||||
}
|
||||
|
||||
private <T extends Node> T resolve(String path, Class<T> expectedNodeType) {
|
||||
final String[] pathFragments = StringUtils.split(path, '/');
|
||||
if (ArrayUtils.isEmpty(pathFragments)) {
|
||||
assert expectedNodeType.isAssignableFrom(Folder.class);
|
||||
return expectedNodeType.cast(filesystem);
|
||||
} else {
|
||||
return resolve(filesystem, Arrays.stream(pathFragments).iterator(), expectedNodeType);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Node> T resolve(Folder parent, Iterator<String> pathIterator, Class<T> expectedNodeType) {
|
||||
assert pathIterator.hasNext();
|
||||
final String childName = pathIterator.next();
|
||||
if (pathIterator.hasNext()) {
|
||||
return resolve(parent.folder(childName), pathIterator, expectedNodeType);
|
||||
} else if (expectedNodeType.isAssignableFrom(Folder.class)) {
|
||||
return expectedNodeType.cast(parent.folder(childName));
|
||||
} else if (expectedNodeType.isAssignableFrom(File.class)) {
|
||||
return expectedNodeType.cast(parent.file(childName));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Supported expectedNodeTypes are File or Folder.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.webdav.jackrabbit;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jackrabbit.webdav.DavLocatorFactory;
|
||||
import org.apache.jackrabbit.webdav.DavResourceLocator;
|
||||
import org.apache.jackrabbit.webdav.util.EncodeUtil;
|
||||
|
||||
/**
|
||||
* A LocatorFactory constructing Locators, whose {@link DavResourceLocator#getResourcePath() resourcePath} and {@link DavResourceLocator#getRepositoryPath() repositoryPath} are equal.
|
||||
* These paths will be plain, case-sensitive, absolute, unencoded Strings with Unix-style path separators.
|
||||
*/
|
||||
class IdentityLocatorFactory implements DavLocatorFactory {
|
||||
|
||||
private final String pathPrefix;
|
||||
|
||||
public IdentityLocatorFactory(URI contextRootUri) {
|
||||
this.pathPrefix = StringUtils.removeEnd(contextRootUri.toString(), "/");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResourceLocator createResourceLocator(String prefix, String href) {
|
||||
final String fullPrefix = StringUtils.removeEnd(prefix, "/");
|
||||
final String remainingHref = StringUtils.removeStart(href, fullPrefix);
|
||||
final String unencodedRemaingingHref = EncodeUtil.unescape(remainingHref);
|
||||
assert unencodedRemaingingHref.startsWith("/");
|
||||
return new IdentityLocator(unencodedRemaingingHref);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String resourcePath) {
|
||||
assert resourcePath.startsWith("/");
|
||||
return new IdentityLocator(resourcePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String path, boolean isResourcePath) {
|
||||
assert path.startsWith("/");
|
||||
return new IdentityLocator(path);
|
||||
}
|
||||
|
||||
private class IdentityLocator implements DavResourceLocator {
|
||||
|
||||
private final String absPath;
|
||||
|
||||
private IdentityLocator(String absPath) {
|
||||
assert absPath.startsWith("/");
|
||||
this.absPath = FilenameUtils.normalize(absPath, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix() {
|
||||
return pathPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourcePath() {
|
||||
return absPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWorkspacePath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWorkspaceName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameWorkspace(DavResourceLocator locator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameWorkspace(String workspaceName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHref(boolean isCollection) {
|
||||
final String encodedResourcePath = EncodeUtil.escapePath(absPath);
|
||||
if (isRootLocation()) {
|
||||
return pathPrefix + "/";
|
||||
} else {
|
||||
assert isCollection ? encodedResourcePath.endsWith("/") : true;
|
||||
return pathPrefix + encodedResourcePath;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRootLocation() {
|
||||
return "/".equals(absPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavLocatorFactory getFactory() {
|
||||
return IdentityLocatorFactory.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRepositoryPath() {
|
||||
return absPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Locator: " + absPath + " (Prefix: " + pathPrefix + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return absPath.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof IdentityLocator) {
|
||||
final IdentityLocator other = (IdentityLocator) obj;
|
||||
final boolean samePrefix = this.getPrefix() == null && other.getPrefix() == null || this.getPrefix().equals(other.getPrefix());
|
||||
final boolean sameRelativeCleartextPath = this.absPath == null && other.absPath == null || this.absPath.equals(other.absPath);
|
||||
return samePrefix && sameRelativeCleartextPath;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.webdav.jackrabbit;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.jackrabbit.webdav.DavLocatorFactory;
|
||||
import org.apache.jackrabbit.webdav.DavResource;
|
||||
import org.apache.jackrabbit.webdav.DavResourceFactory;
|
||||
import org.apache.jackrabbit.webdav.DavSessionProvider;
|
||||
import org.apache.jackrabbit.webdav.WebdavRequest;
|
||||
import org.apache.jackrabbit.webdav.server.AbstractWebdavServlet;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
|
||||
public class WebDavServlet extends AbstractWebdavServlet {
|
||||
|
||||
private static final long serialVersionUID = -6632687979352625020L;
|
||||
|
||||
private final DavSessionProvider davSessionProvider;
|
||||
private final DavLocatorFactory davLocatorFactory;
|
||||
private final DavResourceFactory davResourceFactory;
|
||||
|
||||
public WebDavServlet(URI contextRootUri, FileSystem filesystem) {
|
||||
davSessionProvider = new DavSessionProviderImpl();
|
||||
davLocatorFactory = new IdentityLocatorFactory(contextRootUri);
|
||||
davResourceFactory = new FilesystemResourceFactory(filesystem);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isPreconditionValid(WebdavRequest request, DavResource resource) {
|
||||
return !resource.exists() || request.matchesIfHeader(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavSessionProvider getDavSessionProvider() {
|
||||
return davSessionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDavSessionProvider(DavSessionProvider davSessionProvider) {
|
||||
throw new UnsupportedOperationException("Setting davSessionProvider not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavLocatorFactory getLocatorFactory() {
|
||||
return davLocatorFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocatorFactory(DavLocatorFactory locatorFactory) {
|
||||
throw new UnsupportedOperationException("Setting locatorFactory not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResourceFactory getResourceFactory() {
|
||||
return davResourceFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceFactory(DavResourceFactory resourceFactory) {
|
||||
throw new UnsupportedOperationException("Setting resourceFactory not supported.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
Copyright (c) 2014 Markus Kreusch
|
||||
This file is licensed under the terms of the MIT license.
|
||||
See the LICENSE.txt file for more info.
|
||||
|
||||
Contributors:
|
||||
Sebastian Stenzel - log4j config for WebDAV unit tests
|
||||
-->
|
||||
<Configuration status="WARN">
|
||||
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
|
||||
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
|
||||
</Console>
|
||||
<Console name="StdErr" target="SYSTEM_ERR">
|
||||
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
|
||||
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
|
||||
</Console>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<!-- show our own debug messages: -->
|
||||
<Logger name="org.cryptomator" level="DEBUG" />
|
||||
<Logger name="org.eclipse.jetty.server.Server" level="DEBUG" />
|
||||
<!-- mute dependencies: -->
|
||||
<Root level="INFO">
|
||||
<AppenderRef ref="Console" />
|
||||
<AppenderRef ref="StdErr" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
|
||||
</Configuration>
|
||||
@@ -0,0 +1,84 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Sebastian Stenzel and others.
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.webdav.jackrabbit;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.FolderCreateMode;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ThreadPool;
|
||||
|
||||
public class InMemoryWebDavServer {
|
||||
|
||||
private final Server server;
|
||||
private final ServerConnector localConnector;
|
||||
private final ContextHandlerCollection servletCollection;
|
||||
private final FileSystem inMemoryFileSystem = new InMemoryFileSystem();
|
||||
|
||||
private InMemoryWebDavServer() {
|
||||
final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(10);
|
||||
final ThreadPool tp = new QueuedThreadPool(4, 1, 1, queue);
|
||||
server = new Server(tp);
|
||||
localConnector = new ServerConnector(server);
|
||||
localConnector.setHost("localhost");
|
||||
localConnector.setPort(8080);
|
||||
servletCollection = new ContextHandlerCollection();
|
||||
|
||||
URI servletContextRootUri;
|
||||
try {
|
||||
servletContextRootUri = new URI("http", null, "localhost", 8080, "/", null, null);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
final ServletContextHandler servletContext = new ServletContextHandler(servletCollection, "/", ServletContextHandler.SESSIONS);
|
||||
final ServletHolder servletHolder = new ServletHolder("InMemory-WebDAV-Servlet", new WebDavServlet(servletContextRootUri, inMemoryFileSystem));
|
||||
servletContext.addServlet(servletHolder, "/*");
|
||||
servletCollection.mapContexts();
|
||||
|
||||
server.setConnectors(new Connector[] {localConnector});
|
||||
server.setHandler(servletCollection);
|
||||
}
|
||||
|
||||
private void start() throws Exception {
|
||||
server.start();
|
||||
}
|
||||
|
||||
private void stop() throws Exception {
|
||||
server.stop();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
final InMemoryWebDavServer server = new InMemoryWebDavServer();
|
||||
|
||||
server.inMemoryFileSystem.folder("mamals").folder("cats").create(FolderCreateMode.INCLUDING_PARENTS);
|
||||
server.inMemoryFileSystem.folder("mamals").folder("dogs").create(FolderCreateMode.INCLUDING_PARENTS);
|
||||
try (WritableFile writable = server.inMemoryFileSystem.folder("mamals").folder("cats").file("Garfield.txt").openWritable()) {
|
||||
writable.write(ByteBuffer.wrap("meow".getBytes()));
|
||||
}
|
||||
|
||||
server.start();
|
||||
System.out.println("Server started. Press any key to stop it...");
|
||||
System.in.read();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -222,6 +222,7 @@
|
||||
<module>crypto-aes</module>
|
||||
<module>core</module>
|
||||
<module>ui</module>
|
||||
<module>jackrabbit-filesystem-adapter</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
|
||||
Reference in New Issue
Block a user