package org.argeo.jcr.fs;
import java.io.IOException;
-import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessMode;
import java.nio.file.CopyOption;
+import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
+import java.util.Calendar;
+import java.util.HashMap;
import java.util.Map;
import java.util.Set;
-public class JcrFileSystemProvider extends FileSystemProvider {
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.PropertyDefinition;
- @Override
- public String getScheme() {
- return "jcr";
- }
-
- @Override
- public FileSystem newFileSystem(URI uri, Map<String, ?> env)
- throws IOException {
- // TODO Auto-generated method stub
- return null;
- }
+import org.argeo.jcr.JcrUtils;
- @Override
- public FileSystem getFileSystem(URI uri) {
- // TODO Auto-generated method stub
- return null;
- }
+/** Operations on a {@link JcrFileSystem}. */
+public abstract class JcrFileSystemProvider extends FileSystemProvider {
@Override
- public Path getPath(URI uri) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public SeekableByteChannel newByteChannel(Path path,
- Set<? extends OpenOption> options, FileAttribute<?>... attrs)
+ public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
throws IOException {
- // TODO Auto-generated method stub
- return null;
+ Node node = toNode(path);
+ try {
+ if (node == null) {
+ Node parent = toNode(path.getParent());
+ if (parent == null)
+ throw new IOException("No parent directory for " + path);
+ if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
+ || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
+ throw new IOException(path + " parent is a file");
+
+ String fileName = path.getFileName().toString();
+ fileName = Text.escapeIllegalJcrChars(fileName);
+ node = parent.addNode(fileName, NodeType.NT_FILE);
+ node.addMixin(NodeType.MIX_CREATED);
+// node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ }
+ if (!node.isNodeType(NodeType.NT_FILE))
+ throw new UnsupportedOperationException(node + " must be a file");
+ return new BinaryChannel(node, path);
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot read file", e);
+ }
}
@Override
- public DirectoryStream<Path> newDirectoryStream(Path dir,
- Filter<? super Path> filter) throws IOException {
- // TODO Auto-generated method stub
- return null;
+ public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
+ try {
+ Node base = toNode(dir);
+ if (base == null)
+ throw new IOException(dir + " is not a JCR node");
+ JcrFileSystem fileSystem = (JcrFileSystem) dir.getFileSystem();
+ return new NodeDirectoryStream(fileSystem, base.getNodes(), fileSystem.listDirectMounts(dir), filter);
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot list directory", e);
+ }
}
@Override
- public void createDirectory(Path dir, FileAttribute<?>... attrs)
- throws IOException {
- // TODO Auto-generated method stub
-
+ public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+ Node node = toNode(dir);
+ try {
+ if (node == null) {
+ Node parent = toNode(dir.getParent());
+ if (parent == null)
+ throw new IOException("Parent of " + dir + " does not exist");
+ Session session = parent.getSession();
+ synchronized (session) {
+ if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
+ || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
+ throw new IOException(dir + " parent is a file");
+ String fileName = dir.getFileName().toString();
+ fileName = Text.escapeIllegalJcrChars(fileName);
+ node = parent.addNode(fileName, NodeType.NT_FOLDER);
+ node.addMixin(NodeType.MIX_CREATED);
+ node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ save(session);
+ }
+ } else {
+ // if (!node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER))
+ // throw new FileExistsException(dir + " exists and is not a directory");
+ }
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot create directory " + dir, e);
+ }
}
@Override
public void delete(Path path) throws IOException {
- // TODO Auto-generated method stub
+ Node node = toNode(path);
+ try {
+ if (node == null)
+ throw new NoSuchFileException(path + " does not exist");
+ Session session = node.getSession();
+ synchronized (session) {
+ session.refresh(false);
+ if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE))
+ node.remove();
+ else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) {
+ if (node.hasNodes())// TODO check only files
+ throw new DirectoryNotEmptyException(path.toString());
+ node.remove();
+ }
+ save(session);
+ }
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot delete " + path, e);
+ }
}
@Override
- public void copy(Path source, Path target, CopyOption... options)
- throws IOException {
- // TODO Auto-generated method stub
-
+ public void copy(Path source, Path target, CopyOption... options) throws IOException {
+ Node sourceNode = toNode(source);
+ Node targetNode = toNode(target);
+ try {
+ Session targetSession = targetNode.getSession();
+ synchronized (targetSession) {
+ JcrUtils.copy(sourceNode, targetNode);
+ save(targetSession);
+ }
+ } catch (RepositoryException e) {
+ discardChanges(sourceNode);
+ discardChanges(targetNode);
+ throw new IOException("Cannot copy from " + source + " to " + target, e);
+ }
}
@Override
- public void move(Path source, Path target, CopyOption... options)
- throws IOException {
- // TODO Auto-generated method stub
+ public void move(Path source, Path target, CopyOption... options) throws IOException {
+ JcrFileSystem sourceFileSystem = (JcrFileSystem) source.getFileSystem();
+ WorkspaceFileStore sourceStore = sourceFileSystem.getFileStore(source.toString());
+ WorkspaceFileStore targetStore = sourceFileSystem.getFileStore(target.toString());
+ try {
+ if (sourceStore.equals(targetStore)) {
+ sourceStore.getWorkspace().move(sourceStore.toJcrPath(source.toString()),
+ targetStore.toJcrPath(target.toString()));
+ } else {
+ // TODO implement it
+ throw new UnsupportedOperationException("Can only move paths within the same workspace.");
+ }
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot move from " + source + " to " + target, e);
+ }
+// Node sourceNode = toNode(source);
+// try {
+// Session session = sourceNode.getSession();
+// synchronized (session) {
+// session.move(sourceNode.getPath(), target.toString());
+// save(session);
+// }
+// } catch (RepositoryException e) {
+// discardChanges(sourceNode);
+// throw new IOException("Cannot move from " + source + " to " + target, e);
+// }
}
@Override
public boolean isSameFile(Path path, Path path2) throws IOException {
- // TODO Auto-generated method stub
- return false;
+ if (path.getFileSystem() != path2.getFileSystem())
+ return false;
+ boolean equ = path.equals(path2);
+ if (equ)
+ return true;
+ else {
+ try {
+ Node node = toNode(path);
+ Node node2 = toNode(path2);
+ return node.isSame(node2);
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot check whether " + path + " and " + path2 + " are same", e);
+ }
+ }
+
}
@Override
public boolean isHidden(Path path) throws IOException {
- // TODO Auto-generated method stub
- return false;
+ return path.getFileName().toString().charAt(0) == '.';
}
@Override
public FileStore getFileStore(Path path) throws IOException {
- // TODO Auto-generated method stub
- return null;
+ JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem();
+ return fileSystem.getFileStore(path.toString());
}
@Override
public void checkAccess(Path path, AccessMode... modes) throws IOException {
- // TODO Auto-generated method stub
-
+ Node node = toNode(path);
+ if (node == null)
+ throw new NoSuchFileException(path + " does not exist");
+ // TODO check access via JCR api
}
@Override
- public <V extends FileAttributeView> V getFileAttributeView(Path path,
- Class<V> type, LinkOption... options) {
- // TODO Auto-generated method stub
- return null;
+ public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
+ throw new UnsupportedOperationException();
}
+ @SuppressWarnings("unchecked")
@Override
- public <A extends BasicFileAttributes> A readAttributes(Path path,
- Class<A> type, LinkOption... options) throws IOException {
- // TODO Auto-generated method stub
- return null;
+ public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
+ throws IOException {
+ // TODO check if assignable
+ Node node = toNode(path);
+ if (node == null) {
+ throw new IOException("JCR node not found for " + path);
+ }
+ return (A) new JcrBasicfileAttributes(node);
}
@Override
- public Map<String, Object> readAttributes(Path path, String attributes,
- LinkOption... options) throws IOException {
- // TODO Auto-generated method stub
- return null;
+ public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+ try {
+ Node node = toNode(path);
+ String pattern = attributes.replace(',', '|');
+ Map<String, Object> res = new HashMap<String, Object>();
+ PropertyIterator it = node.getProperties(pattern);
+ props: while (it.hasNext()) {
+ Property prop = it.nextProperty();
+ PropertyDefinition pd = prop.getDefinition();
+ if (pd.isMultiple())
+ continue props;
+ int requiredType = pd.getRequiredType();
+ switch (requiredType) {
+ case PropertyType.LONG:
+ res.put(prop.getName(), prop.getLong());
+ break;
+ case PropertyType.DOUBLE:
+ res.put(prop.getName(), prop.getDouble());
+ break;
+ case PropertyType.BOOLEAN:
+ res.put(prop.getName(), prop.getBoolean());
+ break;
+ case PropertyType.DATE:
+ res.put(prop.getName(), prop.getDate());
+ break;
+ case PropertyType.BINARY:
+ byte[] arr = JcrUtils.getBinaryAsBytes(prop);
+ res.put(prop.getName(), arr);
+ break;
+ default:
+ res.put(prop.getName(), prop.getString());
+ }
+ }
+ return res;
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot read attributes of " + path, e);
+ }
}
@Override
- public void setAttribute(Path path, String attribute, Object value,
- LinkOption... options) throws IOException {
- // TODO Auto-generated method stub
+ public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+ Node node = toNode(path);
+ try {
+ Session session = node.getSession();
+ synchronized (session) {
+ if (value instanceof byte[]) {
+ JcrUtils.setBinaryAsBytes(node, attribute, (byte[]) value);
+ } else if (value instanceof Calendar) {
+ node.setProperty(attribute, (Calendar) value);
+ } else {
+ node.setProperty(attribute, value.toString());
+ }
+ save(session);
+ }
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot set attribute " + attribute + " on " + path, e);
+ }
+ }
+ protected Node toNode(Path path) {
+ try {
+ return ((JcrPath) path).getNode();
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot convert path " + path + " to JCR Node", e);
+ }
+ }
+
+ /** Discard changes in the underlying session */
+ protected void discardChanges(Node node) {
+ if (node == null)
+ return;
+ try {
+ // discard changes
+ node.getSession().refresh(false);
+ } catch (RepositoryException e) {
+ e.printStackTrace();
+ // TODO log out session?
+ // TODO use Commons logging?
+ }
+ }
+
+ /** Make sure save is robust. */
+ protected void save(Session session) throws RepositoryException {
+ session.refresh(true);
+ session.save();
+ session.notifyAll();
+ }
+
+ /**
+ * To be overriden in order to support the ~ path, with an implementation
+ * specific concept of user home.
+ *
+ * @return null by default
+ */
+ public Node getUserHome(Repository session) {
+ return null;
}
}