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;
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();
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;
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";
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);
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 {
+
+ }
}
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;
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)
}
}
+ 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))
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;
@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
@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
@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
public Node getUserHome(Session session) {
return null;
}
+
}
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;
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;
}
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
@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();
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)
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
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
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
public Path toAbsolutePath() {
if (isAbsolute())
return this;
- return new JcrPath(fs, path, true);
+ return new JcrPath(fs, fileStore, path, true);
}
@Override
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
+++ /dev/null
-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);
- }
-
-}
--- /dev/null
+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();
+ }
+
+}