Introduce multi-workspaces JCR file system.
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 29 Feb 2020 08:38:12 +0000 (09:38 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 29 Feb 2020 08:38:12 +0000 (09:38 +0100)
org.argeo.jcr/ext/test/org/argeo/jcr/fs/JcrFileSystemTest.java
org.argeo.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java
org.argeo.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java
org.argeo.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java
org.argeo.jcr/src/org/argeo/jcr/fs/JcrPath.java
org.argeo.jcr/src/org/argeo/jcr/fs/WorkSpaceFileStore.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java [new file with mode: 0644]

index 190eb949a0f0bcacc4308470a0725d96d5922051..886efa3675450c7c14faf7c1fc1346bdcc7a4078 100644 (file)
@@ -13,11 +13,14 @@ import java.util.Arrays;
 import java.util.Map;
 
 import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
 import javax.jcr.nodetype.NodeType;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.core.RepositoryImpl;
 import org.argeo.jackrabbit.fs.JackrabbitMemoryFsProvider;
 
 import junit.framework.TestCase;
@@ -25,6 +28,38 @@ import junit.framework.TestCase;
 public class JcrFileSystemTest extends TestCase {
        private final static Log log = LogFactory.getLog(JcrFileSystemTest.class);
 
+       public void testMounts() throws Exception {
+               JackrabbitMemoryFsProvider fsProvider = new JackrabbitMemoryFsProvider() {
+
+                       @Override
+                       protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException {
+                               // create workspace
+                               Session session = login();
+                               session.getWorkspace().createWorkspace("test");
+                       }
+
+               };
+
+               Path rootPath = fsProvider.getPath(new URI("jcr+memory:/"));
+               log.debug("Got root " + rootPath);
+
+               Path testMount = fsProvider.getPath(new URI("jcr+memory:/test"));
+               log.debug("Test path");
+               assertEquals(rootPath, testMount.getParent());
+               assertEquals(testMount.getFileName(), rootPath.relativize(testMount));
+
+               Path testPath = testMount.resolve("test.txt");
+               log.debug("Create file " + testPath);
+               Files.createFile(testPath);
+               BasicFileAttributes bfa = Files.readAttributes(testPath, BasicFileAttributes.class);
+               FileTime ft = bfa.creationTime();
+               assertNotNull(ft);
+               assertTrue(bfa.isRegularFile());
+               log.debug("Created " + testPath + " (" + ft + ")");
+               Files.delete(testPath);
+               log.debug("Deleted " + testPath);
+       }
+
        public void testSimple() throws Exception {
                FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
 
index 47cf33d142de12d8b3a2a848b721c5efe0beba3b..e3a70d0842165d11a447f19380b67a7e2c77278e 100644 (file)
@@ -10,6 +10,8 @@ import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.jcr.Credentials;
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
@@ -23,6 +25,13 @@ public class JackrabbitMemoryFsProvider extends AbstractJackrabbitFsProvider {
        private RepositoryImpl repository;
        private JcrFileSystem fileSystem;
 
+       private Credentials credentials;
+
+       public JackrabbitMemoryFsProvider() {
+               String username = System.getProperty("user.name");
+               credentials = new SimpleCredentials(username, username.toCharArray());
+       }
+
        @Override
        public String getScheme() {
                return "jcr+memory";
@@ -32,12 +41,11 @@ public class JackrabbitMemoryFsProvider extends AbstractJackrabbitFsProvider {
        public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
                try {
                        Path tempDir = Files.createTempDirectory("fs-memory");
-                       URL confUrl = getClass().getResource("fs-memory.xml");
+                       URL confUrl = JackrabbitMemoryFsProvider.class.getResource("fs-memory.xml");
                        RepositoryConfig repositoryConfig = RepositoryConfig.create(confUrl.toURI(), tempDir.toString());
                        repository = RepositoryImpl.create(repositoryConfig);
-                       String username = System.getProperty("user.name");
-                       Session session = repository.login(new SimpleCredentials(username, username.toCharArray()));
-                       fileSystem = new JcrFileSystem(this, session);
+                       postRepositoryCreation(repository);
+                       fileSystem = new JcrFileSystem(this, repository, credentials);
                        return fileSystem;
                } catch (RepositoryException | URISyntaxException e) {
                        throw new IOException("Cannot login to repository", e);
@@ -61,4 +69,19 @@ public class JackrabbitMemoryFsProvider extends AbstractJackrabbitFsProvider {
                return fileSystem.getPath(path);
        }
 
+       public Repository getRepository() {
+               return repository;
+       }
+
+       public Session login() throws RepositoryException {
+               return getRepository().login(credentials);
+       }
+
+       /**
+        * Called after the repository has been created and before the file system is
+        * created.
+        */
+       protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException {
+
+       }
 }
index 67371d08fdbcaef92ad2b27e892637e9d228f9c6..4c8355f97cf10912ffc5a7fc78036087121750da 100644 (file)
@@ -8,10 +8,16 @@ import java.nio.file.PathMatcher;
 import java.nio.file.WatchService;
 import java.nio.file.attribute.UserPrincipalLookupService;
 import java.nio.file.spi.FileSystemProvider;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 
+import javax.jcr.Credentials;
 import javax.jcr.Node;
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.nodetype.NodeType;
@@ -20,12 +26,19 @@ import org.argeo.jcr.JcrUtils;
 
 public class JcrFileSystem extends FileSystem {
        private final JcrFileSystemProvider provider;
+
        private final Session session;
+       private WorkspaceFileStore baseFileStore;
+
+       private Map<String, WorkspaceFileStore> mounts = new TreeMap<>();
+
        private String userHomePath = null;
 
+       @Deprecated
        public JcrFileSystem(JcrFileSystemProvider provider, Session session) throws IOException {
                super();
                this.provider = provider;
+               baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
                this.session = session;
                Node userHome = provider.getUserHome(session);
                if (userHome != null)
@@ -36,6 +49,38 @@ public class JcrFileSystem extends FileSystem {
                        }
        }
 
+       public JcrFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException {
+               this(provider, repository, null);
+       }
+
+       public JcrFileSystem(JcrFileSystemProvider provider, Repository repository, Credentials credentials)
+                       throws IOException {
+               super();
+               this.provider = provider;
+               try {
+                       this.session = credentials == null ? repository.login() : repository.login(credentials);
+                       baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
+                       workspaces: for (String workspaceName : baseFileStore.getWorkspace().getAccessibleWorkspaceNames()) {
+                               if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
+                                       continue workspaces;// do not mount base
+                               Session mountSession = credentials == null ? repository.login(workspaceName)
+                                               : repository.login(credentials, workspaceName);
+                               String mountPath = JcrPath.separator + workspaceName;
+                               mounts.put(mountPath, new WorkspaceFileStore(mountPath, mountSession.getWorkspace()));
+                       }
+               } catch (RepositoryException e) {
+                       throw new IOException("Cannot initialise file system", e);
+               }
+
+               Node userHome = provider.getUserHome(session);
+               if (userHome != null)
+                       try {
+                               userHomePath = userHome.getPath();
+                       } catch (RepositoryException e) {
+                               throw new IOException("Cannot retrieve user home path", e);
+                       }
+       }
+
        /** Whether this node should be skipped in directory listings */
        public boolean skipNode(Node node) throws RepositoryException {
                if (node.isNodeType(NodeType.NT_HIERARCHY_NODE))
@@ -47,6 +92,34 @@ public class JcrFileSystem extends FileSystem {
                return userHomePath;
        }
 
+       public WorkspaceFileStore getFileStore(String path) {
+               WorkspaceFileStore res = baseFileStore;
+               for (String mountPath : mounts.keySet()) {
+                       if (path.startsWith(mountPath)) {
+                               res = mounts.get(mountPath);
+                               // we keep the last one
+                       }
+               }
+               assert res != null;
+               return res;
+       }
+
+       public WorkspaceFileStore getFileStore(Node node) throws RepositoryException {
+               String workspaceName = node.getSession().getWorkspace().getName();
+               if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
+                       return baseFileStore;
+               for (String mountPath : mounts.keySet()) {
+                       WorkspaceFileStore fileStore = mounts.get(mountPath);
+                       if (workspaceName.equals(fileStore.getWorkspace().getName()))
+                               return fileStore;
+               }
+               throw new IllegalStateException("No workspace mount found for " + node + " in workspace " + workspaceName);
+       }
+
+       public WorkspaceFileStore getBaseFileStore() {
+               return baseFileStore;
+       }
+
        @Override
        public FileSystemProvider provider() {
                return provider;
@@ -55,6 +128,14 @@ public class JcrFileSystem extends FileSystem {
        @Override
        public void close() throws IOException {
                JcrUtils.logoutQuietly(session);
+               for (String mountPath : mounts.keySet()) {
+                       WorkspaceFileStore fileStore = mounts.get(mountPath);
+                       try {
+                               fileStore.close();
+                       } catch (Exception e) {
+                               e.printStackTrace();
+                       }
+               }
        }
 
        @Override
@@ -69,23 +150,22 @@ public class JcrFileSystem extends FileSystem {
 
        @Override
        public String getSeparator() {
-               return "/";
+               return JcrPath.separator;
        }
 
        @Override
        public Iterable<Path> getRootDirectories() {
-               try {
-                       Set<Path> single = new HashSet<>();
-                       single.add(new JcrPath(this, session.getRootNode()));
-                       return single;
-               } catch (RepositoryException e) {
-                       throw new JcrFsException("Cannot get root path", e);
-               }
+               Set<Path> single = new HashSet<>();
+               single.add(new JcrPath(this, JcrPath.separator));
+               return single;
        }
 
        @Override
        public Iterable<FileStore> getFileStores() {
-               throw new UnsupportedOperationException();
+               List<FileStore> stores = new ArrayList<>();
+               stores.add(baseFileStore);
+               stores.addAll(mounts.values());
+               return stores;
        }
 
        @Override
index bd0befe7c6c3346bdbc946b9010e4d21c4f35f79..a9ea14826d2bc7c1937a6f8a9390f1f6ab588b84 100644 (file)
@@ -188,8 +188,8 @@ public abstract class JcrFileSystemProvider extends FileSystemProvider {
 
        @Override
        public FileStore getFileStore(Path path) throws IOException {
-               Session session = ((JcrFileSystem) path.getFileSystem()).getSession();
-               return new WorkSpaceFileStore(session.getWorkspace());
+               JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem();
+               return fileSystem.getFileStore(path.toString());
        }
 
        @Override
@@ -320,4 +320,5 @@ public abstract class JcrFileSystemProvider extends FileSystemProvider {
        public Node getUserHome(Session session) {
                return null;
        }
+
 }
index c5d86f679970e012a54da1b5e7d21dc8f90541a4..1a4d747067b1a20baa29d35683256c9efa3dde68 100644 (file)
@@ -17,14 +17,15 @@ import java.util.NoSuchElementException;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
-import javax.jcr.Session;
 
 /** A {@link Path} which contains a reference to a JCR {@link Node}. */
 public class JcrPath implements Path {
-       private final static String delimStr = "/";
-       private final static char delimChar = '/';
+       final static String separator = "/";
+       final static char separatorChar = '/';
 
        private final JcrFileSystem fs;
+       /** null for non absolute paths */
+       private final WorkspaceFileStore fileStore;
        private final String[] path;// null means root
        private final boolean absolute;
 
@@ -35,14 +36,16 @@ public class JcrPath implements Path {
                this.fs = filesSystem;
                if (path == null)
                        throw new JcrFsException("Path cannot be null");
-               if (path.equals(delimStr)) {// root
+               if (path.equals(separator)) {// root
                        this.path = null;
                        this.absolute = true;
                        this.hashCode = 0;
+                       this.fileStore = fs.getBaseFileStore();
                        return;
                } else if (path.equals("")) {// empty path
                        this.path = new String[] { "" };
                        this.absolute = false;
+                       this.fileStore = null;
                        this.hashCode = "".hashCode();
                        return;
                }
@@ -53,26 +56,36 @@ public class JcrPath implements Path {
                                throw new JcrFsException("No home directory available");
                }
 
-               this.absolute = path.charAt(0) == delimChar ? true : false;
+               this.absolute = path.charAt(0) == separatorChar ? true : false;
+
+               this.fileStore = absolute ? fs.getFileStore(path) : null;
+
                String trimmedPath = path.substring(absolute ? 1 : 0,
-                               path.charAt(path.length() - 1) == delimChar ? path.length() - 1 : path.length());
-               this.path = trimmedPath.split(delimStr);
+                               path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length());
+               this.path = trimmedPath.split(separator);
                for (int i = 0; i < this.path.length; i++) {
                        this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]);
                }
                this.hashCode = this.path[this.path.length - 1].hashCode();
+               assert !(absolute && fileStore == null);
        }
 
        public JcrPath(JcrFileSystem filesSystem, Node node) throws RepositoryException {
-               this(filesSystem, node.getPath());
+               this(filesSystem, filesSystem.getFileStore(node).toFsPath(node));
        }
 
        /** Internal optimisation */
-       private JcrPath(JcrFileSystem filesSystem, String[] path, boolean absolute) {
+       private JcrPath(JcrFileSystem filesSystem, WorkspaceFileStore fileStore, String[] path, boolean absolute) {
                this.fs = filesSystem;
                this.path = path;
                this.absolute = path == null ? true : absolute;
+               if (this.absolute && fileStore == null)
+                       throw new IllegalArgumentException("Absolute path requires a file store");
+               if (!this.absolute && fileStore != null)
+                       throw new IllegalArgumentException("A file store should not be provided for a relative path");
+               this.fileStore = fileStore;
                this.hashCode = path == null ? 0 : path[path.length - 1].hashCode();
+               assert !(absolute && fileStore == null);
        }
 
        @Override
@@ -87,31 +100,17 @@ public class JcrPath implements Path {
 
        @Override
        public Path getRoot() {
-               try {
-                       if (path == null)
-                               return this;
-                       return new JcrPath(fs, fs.getSession().getRootNode());
-               } catch (RepositoryException e) {
-                       throw new JcrFsException("Cannot get root", e);
-               }
+               if (path == null)
+                       return this;
+               return new JcrPath(fs, separator);
        }
 
        @Override
        public String toString() {
-               if (path == null)
-                       return "/";
-               StringBuilder sb = new StringBuilder();
-               if (isAbsolute())
-                       sb.append('/');
-               for (int i = 0; i < path.length; i++) {
-                       if (i != 0)
-                               sb.append('/');
-                       sb.append(path[i]);
-               }
-               return sb.toString();
+               return toFsPath(path);
        }
 
-       public String toJcrPath() {
+       private String toFsPath(String[] path) {
                if (path == null)
                        return "/";
                StringBuilder sb = new StringBuilder();
@@ -120,11 +119,31 @@ public class JcrPath implements Path {
                for (int i = 0; i < path.length; i++) {
                        if (i != 0)
                                sb.append('/');
-                       sb.append(Text.escapeIllegalJcrChars(path[i]));
+                       sb.append(path[i]);
                }
                return sb.toString();
        }
 
+//     @Deprecated
+//     private String toJcrPath() {
+//             return toJcrPath(path);
+//     }
+//
+//     @Deprecated
+//     private String toJcrPath(String[] path) {
+//             if (path == null)
+//                     return "/";
+//             StringBuilder sb = new StringBuilder();
+//             if (isAbsolute())
+//                     sb.append('/');
+//             for (int i = 0; i < path.length; i++) {
+//                     if (i != 0)
+//                             sb.append('/');
+//                     sb.append(Text.escapeIllegalJcrChars(path[i]));
+//             }
+//             return sb.toString();
+//     }
+
        @Override
        public Path getFileName() {
                if (path == null)
@@ -137,9 +156,12 @@ public class JcrPath implements Path {
                if (path == null)
                        return null;
                if (path.length == 1)// root
-                       return new JcrPath(fs, delimStr);
+                       return new JcrPath(fs, separator);
                String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1);
-               return new JcrPath(fs, parentPath, absolute);
+               if (!absolute)
+                       return new JcrPath(fs, null, parentPath, absolute);
+               else
+                       return new JcrPath(fs, toFsPath(parentPath));
        }
 
        @Override
@@ -161,7 +183,7 @@ public class JcrPath implements Path {
                if (path == null)
                        return null;
                String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex);
-               return new JcrPath(fs, parentPath, false);
+               return new JcrPath(fs, null, parentPath, false);
        }
 
        @Override
@@ -204,7 +226,11 @@ public class JcrPath implements Path {
                        System.arraycopy(path, 0, newPath, 0, path.length);
                        System.arraycopy(otherPath.path, 0, newPath, path.length, otherPath.path.length);
                }
-               return new JcrPath(fs, newPath, absolute);
+               if (!absolute)
+                       return new JcrPath(fs, null, newPath, absolute);
+               else {
+                       return new JcrPath(fs, toFsPath(newPath));
+               }
        }
 
        @Override
@@ -281,7 +307,7 @@ public class JcrPath implements Path {
        public Path toAbsolutePath() {
                if (isAbsolute())
                        return this;
-               return new JcrPath(fs, path, true);
+               return new JcrPath(fs, fileStore, path, true);
        }
 
        @Override
@@ -313,13 +339,15 @@ public class JcrPath implements Path {
 
        public Node getNode() throws RepositoryException {
                if (!isAbsolute())// TODO default dir
-                       throw new JcrFsException("Cannot get node from relative path");
-               String pathStr = toJcrPath();
-               Session session = fs.getSession();
-               // TODO synchronize on the session ?
-               if (!session.itemExists(pathStr))
-                       return null;
-               return session.getNode(pathStr);
+                       throw new JcrFsException("Cannot get a JCR node from a relative path");
+               assert fileStore != null;
+               return fileStore.toNode(path);
+//             String pathStr = toJcrPath();
+//             Session session = fs.getSession();
+//             // TODO synchronize on the session ?
+//             if (!session.itemExists(pathStr))
+//                     return null;
+//             return session.getNode(pathStr);
        }
 
        @Override
diff --git a/org.argeo.jcr/src/org/argeo/jcr/fs/WorkSpaceFileStore.java b/org.argeo.jcr/src/org/argeo/jcr/fs/WorkSpaceFileStore.java
deleted file mode 100644 (file)
index bdefff3..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.file.FileStore;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileStoreAttributeView;
-
-import javax.jcr.Workspace;
-
-public class WorkSpaceFileStore extends FileStore {
-       private Workspace workspace;
-
-       public WorkSpaceFileStore(Workspace workspace) {
-               this.workspace = workspace;
-       }
-
-       @Override
-       public String name() {
-               return workspace.getName();
-       }
-
-       @Override
-       public String type() {
-               return "workspace";
-       }
-
-       @Override
-       public boolean isReadOnly() {
-               return false;
-       }
-
-       @Override
-       public long getTotalSpace() throws IOException {
-               return 0;
-       }
-
-       @Override
-       public long getUsableSpace() throws IOException {
-               return 0;
-       }
-
-       @Override
-       public long getUnallocatedSpace() throws IOException {
-               return 0;
-       }
-
-       @Override
-       public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
-               return false;
-       }
-
-       @Override
-       public boolean supportsFileAttributeView(String name) {
-               return false;
-       }
-
-       @Override
-       public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
-               return null;
-       }
-
-       @Override
-       public Object getAttribute(String attribute) throws IOException {
-               return workspace.getSession().getRepository().getDescriptor(attribute);
-       }
-
-}
diff --git a/org.argeo.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java b/org.argeo.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java
new file mode 100644 (file)
index 0000000..0b81d55
--- /dev/null
@@ -0,0 +1,149 @@
+package org.argeo.jcr.fs;
+
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileStoreAttributeView;
+import java.util.Arrays;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Workspace;
+
+import org.argeo.jcr.JcrUtils;
+
+/** A {@link FileStore} implementation based on JCR {@link Workspace}. */
+public class WorkspaceFileStore extends FileStore {
+       private final String mountPath;
+       private final Workspace workspace;
+       private final int mountDepth;
+
+       public WorkspaceFileStore(String mountPath, Workspace workspace) {
+               if ("/".equals(mountPath) || "".equals(mountPath))
+                       throw new IllegalArgumentException(
+                                       "Mount path '" + mountPath + "' is unsupported, use null for the base file store");
+               if (mountPath != null && !mountPath.startsWith(JcrPath.separator))
+                       throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
+               if (mountPath != null && mountPath.endsWith(JcrPath.separator))
+                       throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
+               this.mountPath = mountPath;
+               if (mountPath == null)
+                       mountDepth = 0;
+               else {
+                       mountDepth = mountPath.split(JcrPath.separator).length - 1;
+               }
+               this.workspace = workspace;
+       }
+
+       public void close() {
+               JcrUtils.logoutQuietly(workspace.getSession());
+       }
+
+       @Override
+       public String name() {
+               return workspace.getName();
+       }
+
+       @Override
+       public String type() {
+               return "workspace";
+       }
+
+       @Override
+       public boolean isReadOnly() {
+               return false;
+       }
+
+       @Override
+       public long getTotalSpace() throws IOException {
+               return 0;
+       }
+
+       @Override
+       public long getUsableSpace() throws IOException {
+               return 0;
+       }
+
+       @Override
+       public long getUnallocatedSpace() throws IOException {
+               return 0;
+       }
+
+       @Override
+       public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
+               return false;
+       }
+
+       @Override
+       public boolean supportsFileAttributeView(String name) {
+               return false;
+       }
+
+       @Override
+       public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
+               return null;
+       }
+
+       @Override
+       public Object getAttribute(String attribute) throws IOException {
+               return workspace.getSession().getRepository().getDescriptor(attribute);
+       }
+
+       public Workspace getWorkspace() {
+               return workspace;
+       }
+
+       public String toFsPath(Node node) throws RepositoryException {
+               String nodeWorkspaceName = node.getSession().getWorkspace().getName();
+               if (!nodeWorkspaceName.equals(workspace.getName()))
+                       throw new IllegalArgumentException("Icompatible " + node + " from workspace '" + nodeWorkspaceName
+                                       + "' in file store '" + workspace.getName() + "'");
+               return mountPath == null ? node.getPath() : mountPath + node.getPath();
+       }
+
+       public boolean isBase() {
+               return mountPath == null;
+       }
+
+       Node toNode(String[] fullPath) throws RepositoryException {
+               String jcrPath = toJcrPath(fullPath);
+               Session session = workspace.getSession();
+               if (!session.itemExists(jcrPath))
+                       return null;
+               Node node = session.getNode(jcrPath);
+               return node;
+       }
+
+       private String toJcrPath(String[] path) {
+               if (path == null)
+                       return "/";
+               if (path.length < mountDepth)
+                       throw new IllegalArgumentException(
+                                       "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
+
+               if (!isBase()) {
+                       // check mount compatibility
+                       StringBuilder mount = new StringBuilder();
+                       mount.append('/');
+                       for (int i = 0; i < mountDepth; i++) {
+                               if (i != 0)
+                                       mount.append('/');
+                               mount.append(Text.escapeIllegalJcrChars(path[i]));
+                       }
+                       if (!mountPath.equals(mount.toString()))
+                               throw new IllegalArgumentException(
+                                               "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
+               }
+
+               StringBuilder sb = new StringBuilder();
+               sb.append('/');
+               for (int i = mountDepth; i < path.length; i++) {
+                       if (i != mountDepth)
+                               sb.append('/');
+                       sb.append(Text.escapeIllegalJcrChars(path[i]));
+               }
+               return sb.toString();
+       }
+
+}