From e67c8f2816df64595afccb1dd432cee7d80aa4c8 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 24 Dec 2015 00:32:07 +0100 Subject: [PATCH] first experiments with jackrabbit-filesystem-adapter --- main/jackrabbit-filesystem-adapter/pom.xml | 78 ++++++ .../webdav/jackrabbit/DavFile.java | 98 ++++++++ .../webdav/jackrabbit/DavFolder.java | 104 ++++++++ .../webdav/jackrabbit/DavNode.java | 228 ++++++++++++++++++ .../webdav/jackrabbit/DavSessionImpl.java | 45 ++++ .../jackrabbit/DavSessionProviderImpl.java | 36 +++ .../jackrabbit/FilesystemResourceFactory.java | 92 +++++++ .../jackrabbit/IdentityLocatorFactory.java | 140 +++++++++++ .../webdav/jackrabbit/WebDavServlet.java | 70 ++++++ .../src/main/resources/log4j2.xml | 34 +++ .../jackrabbit/InMemoryWebDavServer.java | 84 +++++++ main/pom.xml | 1 + 12 files changed, 1010 insertions(+) create mode 100644 main/jackrabbit-filesystem-adapter/pom.xml create mode 100644 main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavFile.java create mode 100644 main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavFolder.java create mode 100644 main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavNode.java create mode 100644 main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionImpl.java create mode 100644 main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionProviderImpl.java create mode 100644 main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/FilesystemResourceFactory.java create mode 100644 main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/IdentityLocatorFactory.java create mode 100644 main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java create mode 100644 main/jackrabbit-filesystem-adapter/src/main/resources/log4j2.xml create mode 100644 main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbit/InMemoryWebDavServer.java diff --git a/main/jackrabbit-filesystem-adapter/pom.xml b/main/jackrabbit-filesystem-adapter/pom.xml new file mode 100644 index 000000000..8eed17c99 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/pom.xml @@ -0,0 +1,78 @@ + + + + 4.0.0 + + org.cryptomator + main + 0.11.0-SNAPSHOT + + jackrabbit-filesystem-adapter + Jackrabbit filesystem-WebDAV-adapter + WebDAV servlet based on Apache Jackrabbit serving files from a filesystem + + + 2.11.0 + + + + + + org.cryptomator + filesystem-api + + + + + org.apache.jackrabbit + jackrabbit-webdav + ${jackrabbit.version} + + + + + org.apache.commons + commons-lang3 + + + + + org.eclipse.jetty + jetty-server + 9.3.3.v20150827 + test + + + org.eclipse.jetty + jetty-webapp + 9.3.3.v20150827 + test + + + commons-httpclient + commons-httpclient + test + + + org.cryptomator + filesystem-inmemory + test + + + + + + + org.jacoco + jacoco-maven-plugin + + + + \ No newline at end of file diff --git a/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavFile.java b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavFile.java new file mode 100644 index 000000000..3eb96adfa --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavFile.java @@ -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 { + + 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 + + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavFolder.java b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavFolder.java new file mode 100644 index 000000000..132092699 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavFolder.java @@ -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 { + + 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(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 folders = node.folders().map(this::getMemberFolder); + final Stream 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 + + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavNode.java b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavNode.java new file mode 100644 index 000000000..340f0654d --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavNode.java @@ -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 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 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; + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionImpl.java b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionImpl.java new file mode 100644 index 000000000..61b045a29 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionImpl.java @@ -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 lockTokens = new HashSet(); + private final HashSet references = new HashSet(); + + @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); + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionProviderImpl.java b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionProviderImpl.java new file mode 100644 index 000000000..9f1e059fb --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionProviderImpl.java @@ -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); + } + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/FilesystemResourceFactory.java b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/FilesystemResourceFactory.java new file mode 100644 index 000000000..5811264e2 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/FilesystemResourceFactory.java @@ -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.class; + private static final Class 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 resolve(String path, Class 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 resolve(Folder parent, Iterator pathIterator, Class 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."); + } + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/IdentityLocatorFactory.java b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/IdentityLocatorFactory.java new file mode 100644 index 000000000..ca0895db6 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/IdentityLocatorFactory.java @@ -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; + } + } + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java new file mode 100644 index 000000000..533479601 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java @@ -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."); + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/main/resources/log4j2.xml b/main/jackrabbit-filesystem-adapter/src/main/resources/log4j2.xml new file mode 100644 index 000000000..4f9b18066 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/main/resources/log4j2.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbit/InMemoryWebDavServer.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbit/InMemoryWebDavServer.java new file mode 100644 index 000000000..9d27c643b --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbit/InMemoryWebDavServer.java @@ -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 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(); + } + +} diff --git a/main/pom.xml b/main/pom.xml index f03098771..47f081266 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -222,6 +222,7 @@ crypto-aes core ui + jackrabbit-filesystem-adapter